// Add/remove values from a space-delimited list.
String.prototype.add_value = function(arrAdd, arrRemove, bolAddCaseSensitive, bolRemoveCaseSensitive) {
	return goroam.string.add_value(this, arrAdd, arrRemove, bolAddCaseSensitive, bolRemoveCaseSensitive);
}
// Replace any whitespace with a single space.
String.prototype.compress_whitespace = function() {
	var regReplace = /\s+/g;
	return this.replace(regReplace, ' ');
};
String.prototype.in_str = function(str) {
	return goroam.string.in_str(this, str);
};
String.prototype.left = function(x) {
	return goroam.string.left(this, x);
};
String.prototype.left_of = function(str) {
	return goroam.string.left_of_str(this, str);
};
String.prototype.left_of_rev = function(str) {
	return goroam.string.left_of_str_rev(this, str);
};
String.prototype.ltrim = function() {
	return goroam.string.ltrim(this);
};
String.prototype.md5 = function() {
  return goroam.string.md5(this);
};
String.prototype.reg_exp_escape = function(str) {
  return goroam.string.reg_exp_escape(this);
}
String.prototype.right = function(x) {
	return goroam.string.right(this, x);
};
String.prototype.right_of = function(str) {
	return goroam.string.right_of_str(this, str);
};
String.prototype.right_of_rev = function(str) {
	return goroam.string.right_of_str_rev(this, str);
};
String.prototype.rtrim = function() {
	return goroam.string.rtrim(this);
};
String.prototype.split_trim = function(strDelim) {
	return goroam.string.split_trim(this, strDelim);
};
String.prototype.to_hashtable = function(strMemberDelimiter, strPairDelimiter) {
	return goroam.string.to_hashtable(this, strMemberDelimiter, strPairDelimiter);
};
String.prototype.trim = function() {
	return goroam.string.trim(this);
};


// Adds a prefix to all words in the string.
goroam.string.add_prefix_to_all_words = function(str, strPrefix) {
  return str.replace(/(^|\s)\b/g, '$1' + strPrefix);
}



// Add/remove values from a space-delimited list.
goroam.string.add_value = function(str, arrAdd, arrRemove, bolAddCaseSensitive, bolRemoveCaseSensitive) {
	var i;
	bolAddCaseSensitive = goroam.system.set_default(bolAddCaseSensitive, true);
	bolRemoveCaseSensitive = goroam.system.set_default(bolRemoveCaseSensitive, true);
	// Prep the arrays.
	if (!arrAdd) {
		arrAdd = [];
	} else if (!goroam.system.is_array(arrAdd)) {
		arrAdd = arrAdd.compress_whitespace().split(' ');
	}
	if (!arrRemove) {
		arrRemove = [];
	} else if (!goroam.system.is_array(arrRemove)) {
		arrRemove = arrRemove.compress_whitespace().split(' ');
	}
	// This allows the currently-set value to override, i.e. if a value it set to
	// be both added and removed, nothing will happen and the value will remain
	// in the string if already there and will not be added if already absent.
	arrAdd.remove_intersection(arrRemove);
	// Process the arrays.
	for (i = 0; i < arrAdd.length; i++) {
		if (arrAdd[i] != '') {
			if (!goroam.string.in_str(str, '\\b' + arrAdd[i] + '\\b', bolAddCaseSensitive, true)) str += ' ' + arrAdd[i];
		}
	}
	if (!goroam.system.is_array(arrRemove)) arrRemove = [arrRemove];
	for (i = 0; i < arrRemove.length; i++) {
		if (arrRemove[i] != '') str = goroam.string.replace(str, '\\b' + arrRemove[i] + '\\b', '', bolRemoveCaseSensitive, true);
	}
	return str.compress_whitespace();
}



// Escapes special characters in a string to be used in a regular expression.
goroam.string.reg_exp_escape = function(str) {
	// The list of reserved characters in RegExp.
	var arrSpecials = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '^', '$'];
	// Find the special characters and remember the match.
	var reSpecials = new RegExp('(\\' + arrSpecials.join('|\\') + ')', 'g');
	// Replace the special characters (based on the match) and add a slash before it.
	return str.replace(reSpecials, '\\$1');
}



// See if a string is included in another string.
goroam.string.in_str = function(str1, str2, bolCaseSensitive, bolNoRegExpEscape) {
	var strTemp2 = str2;
	var strFlags = '';
	if (!bolCaseSensitive) strFlags += 'i';
	if (!bolNoRegExpEscape) strTemp2 = strTemp2.reg_exp_escape();
	var reFind = RegExp(strTemp2, strFlags);
	return (str1.search(reFind) >= 0);
}



// Takes the leftmost x characters of a string.
goroam.string.left = function(str, x) {
	return str.substr(0, x);
}



// Takes everything left of a given string.  If the string is not found, it returns the whole string.
goroam.string.left_of_str = function(str, strFind) {
	var i = str.indexOf(strFind);
	if (i < 0) {
		return str;
	} else {
		return str.left(i);
	}
}



// Takes everything left of a given string, starting from the end.  If the string is not found, it returns the whole string.
goroam.string.left_of_str_rev = function(str, strFind) {
	var i = str.lastIndexOf(strFind);
	if (i < 0) {
		return str;
	} else {
		return str.left(i);
	}
}



// Removes leading whitespaces
goroam.string.ltrim = function(str) {
	var re = /\s*((\S+\s*)*)/;
	return str.replace(re, '$1');
}



// This function is similar to string.replace(), except that it automates the RegExp
// and automatically replaces all instances of the found string.  strFind can be
// RegExp string.
goroam.string.replace = function(str, strFind, strReplace, bolCaseSensitive, bolIsRegExp) {
	// Default to case sensitive.
	bolCaseSensitive = goroam.system.set_default(bolCaseSensitive, true);
	// Get the RegExp flags.
	// Match all occurrences.
	var strFlags = 'g';
	// Case in-sensitive.
	if (!bolCaseSensitive) strFlags += 'i';
  var strTemp = strFind;
  if (!bolIsRegExp) strTemp = strTemp.reg_exp_escape();
	var reFind = RegExp(strTemp, strFlags);
	return str.replace(reFind, strReplace);
}



// Takes the righttmost x characters of a string.
goroam.string.right = function(str, x) {
	i = str.length - x;
	if (i < 0) {
		return str;
	} else {
		return str.substr(i);
	}
}



// Takes everything right of a given string.  If the string is not found, it returns the whole string.
goroam.string.right_of_str = function(str, strFind) {
	var i = str.indexOf(strFind);
	if (i < 0) {
		return str;
	} else {
		return str.right(str.length - strFind.length - i);
	}
}



// Takes everything right of a given string, starting from the end.  If the string is not found, it returns the whole string.
goroam.string.right_of_str_rev = function(str, strFind) {
	var i = str.lastIndexOf(strFind);
	if (i < 0) {
		return str;
	} else {
		return str.right(str.length - strFind.length - i);
	}
}



// Removes ending whitespaces
goroam.string.rtrim = function(str) {
	var re = /((\s*\S+)*)\s*/;
	return str.replace(re, '$1');
}



goroam.string.split_trim = function(str, strDelim) {
	var re = RegExp('\\s*' + strDelim + '\\s*', 'g');
	var strTemp = goroam.string.trim(str.replace(re, strDelim));
	return strTemp.split(strDelim);
}



// Translates the given string into a hashtable (an object with properties).  It takes optional values for the
// MemberDelimiter (defaults to '&') and PairDelimiter (defaults to '=').  Defaults essentially translate a string
// to a querystring object.  If a value appears more than once in the string, only the last given value will be
// included in the hashtable.
goroam.string.to_hashtable = function(str, strMemberDelimiter, strPairDelimiter) {
  var objResult = {};
  strMemberDelimiter = (strMemberDelimiter || '&');
  strPairDelimiter = (strPairDelimiter || '=');
  var arrMembers = str.split(strMemberDelimiter);
  for (var i = 0; i < arrMembers.length; i++) {
    var strPair = arrMembers[i].trim();
    if (strPair != '') {
      // Figure out the name/value pair.
      var strName = strPair;
      var strValue = '';
      if (strPair.in_str(strPairDelimiter)) {
        strName = strPair.left_of(strPairDelimiter).trim();
        strValue = strPair.right_of(strPairDelimiter);
      }
      objResult[strName] = strValue;
    }
  }
	return objResult;
}



// Removes leading and ending whitespaces
goroam.string.trim = function(str) {
	return goroam.string.ltrim(goroam.string.rtrim(str));
}



/* MD5 Message-Digest Algorithm - JavaScript
' MODIFICATION HISTORY:
' 1.0    16-Feb-2001 - Phil Fresle (sales@frez.co.uk) - Initial Version (VB/ASP code)
' 1.0    21-Feb-2001 - Enrico Mosanghini (erik504@yahoo.com) - JavaScript porting
*/
goroam.string.md5 = function(sMessage) {
 function RotateLeft(lValue, iShiftBits) { return (lValue<<iShiftBits) | (lValue>>>(32-iShiftBits)); }
 function AddUnsigned(lX,lY) {
	var lX4,lY4,lX8,lY8,lResult;
	lX8 = (lX & 0x80000000);
	lY8 = (lY & 0x80000000);
	lX4 = (lX & 0x40000000);
	lY4 = (lY & 0x40000000);
	lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF);
	if (lX4 & lY4) return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
	if (lX4 | lY4) {
		if (lResult & 0x40000000) return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
		else return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
	} else return (lResult ^ lX8 ^ lY8);
 }
 function F(x,y,z) { return (x & y) | ((~x) & z); }
 function G(x,y,z) { return (x & z) | (y & (~z)); }
 function H(x,y,z) { return (x ^ y ^ z); }
 function I(x,y,z) { return (y ^ (x | (~z))); }
 function FF(a,b,c,d,x,s,ac) {
	a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
	return AddUnsigned(RotateLeft(a, s), b);
 }
 function GG(a,b,c,d,x,s,ac) {
	a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
	return AddUnsigned(RotateLeft(a, s), b);
 }
 function HH(a,b,c,d,x,s,ac) {
	a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
	return AddUnsigned(RotateLeft(a, s), b);
 }
 function II(a,b,c,d,x,s,ac) {
	a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
	return AddUnsigned(RotateLeft(a, s), b);
 }
 function ConvertToWordArray(sMessage) {
	var lWordCount;
	var lMessageLength = sMessage.length;
	var lNumberOfWords_temp1=lMessageLength + 8;
	var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64;
	var lNumberOfWords = (lNumberOfWords_temp2+1)*16;
	var lWordArray=Array(lNumberOfWords-1);
	var lBytePosition = 0;
	var lByteCount = 0;
	while ( lByteCount < lMessageLength ) {
		lWordCount = (lByteCount-(lByteCount % 4))/4;
		lBytePosition = (lByteCount % 4)*8;
		lWordArray[lWordCount] = (lWordArray[lWordCount] | (sMessage.charCodeAt(lByteCount)<<lBytePosition));
		lByteCount++;
	}
	lWordCount = (lByteCount-(lByteCount % 4))/4;
	lBytePosition = (lByteCount % 4)*8;
	lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80<<lBytePosition);
	lWordArray[lNumberOfWords-2] = lMessageLength<<3;
	lWordArray[lNumberOfWords-1] = lMessageLength>>>29;
	return lWordArray;
 }
 function WordToHex(lValue) {
	var WordToHexValue="",WordToHexValue_temp="",lByte,lCount;
	for (lCount = 0;lCount<=3;lCount++) {
		lByte = (lValue>>>(lCount*8)) & 255;
		WordToHexValue_temp = "0" + lByte.toString(16);
		WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2);
	}
	return WordToHexValue;
 }
	var x=Array();
	var k,AA,BB,CC,DD,a,b,c,d
	var S11=7, S12=12, S13=17, S14=22;
	var S21=5, S22=9 , S23=14, S24=20;
	var S31=4, S32=11, S33=16, S34=23;
	var S41=6, S42=10, S43=15, S44=21;
	// Steps 1 and 2.  Append padding bits and length and convert to words
	x = ConvertToWordArray(sMessage);
	// Step 3.  Initialise
	a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
	// Step 4.  Process the message in 16-word blocks
	for (k=0;k<x.length;k+=16) {
		AA=a; BB=b; CC=c; DD=d;
		a=FF(a,b,c,d,x[k+0], S11,0xD76AA478);
		d=FF(d,a,b,c,x[k+1], S12,0xE8C7B756);
		c=FF(c,d,a,b,x[k+2], S13,0x242070DB);
		b=FF(b,c,d,a,x[k+3], S14,0xC1BDCEEE);
		a=FF(a,b,c,d,x[k+4], S11,0xF57C0FAF);
		d=FF(d,a,b,c,x[k+5], S12,0x4787C62A);
		c=FF(c,d,a,b,x[k+6], S13,0xA8304613);
		b=FF(b,c,d,a,x[k+7], S14,0xFD469501);
		a=FF(a,b,c,d,x[k+8], S11,0x698098D8);
		d=FF(d,a,b,c,x[k+9], S12,0x8B44F7AF);
		c=FF(c,d,a,b,x[k+10],S13,0xFFFF5BB1);
		b=FF(b,c,d,a,x[k+11],S14,0x895CD7BE);
		a=FF(a,b,c,d,x[k+12],S11,0x6B901122);
		d=FF(d,a,b,c,x[k+13],S12,0xFD987193);
		c=FF(c,d,a,b,x[k+14],S13,0xA679438E);
		b=FF(b,c,d,a,x[k+15],S14,0x49B40821);
		a=GG(a,b,c,d,x[k+1], S21,0xF61E2562);
		d=GG(d,a,b,c,x[k+6], S22,0xC040B340);
		c=GG(c,d,a,b,x[k+11],S23,0x265E5A51);
		b=GG(b,c,d,a,x[k+0], S24,0xE9B6C7AA);
		a=GG(a,b,c,d,x[k+5], S21,0xD62F105D);
		d=GG(d,a,b,c,x[k+10],S22,0x2441453);
		c=GG(c,d,a,b,x[k+15],S23,0xD8A1E681);
		b=GG(b,c,d,a,x[k+4], S24,0xE7D3FBC8);
		a=GG(a,b,c,d,x[k+9], S21,0x21E1CDE6);
		d=GG(d,a,b,c,x[k+14],S22,0xC33707D6);
		c=GG(c,d,a,b,x[k+3], S23,0xF4D50D87);
		b=GG(b,c,d,a,x[k+8], S24,0x455A14ED);
		a=GG(a,b,c,d,x[k+13],S21,0xA9E3E905);
		d=GG(d,a,b,c,x[k+2], S22,0xFCEFA3F8);
		c=GG(c,d,a,b,x[k+7], S23,0x676F02D9);
		b=GG(b,c,d,a,x[k+12],S24,0x8D2A4C8A);
		a=HH(a,b,c,d,x[k+5], S31,0xFFFA3942);
		d=HH(d,a,b,c,x[k+8], S32,0x8771F681);
		c=HH(c,d,a,b,x[k+11],S33,0x6D9D6122);
		b=HH(b,c,d,a,x[k+14],S34,0xFDE5380C);
		a=HH(a,b,c,d,x[k+1], S31,0xA4BEEA44);
		d=HH(d,a,b,c,x[k+4], S32,0x4BDECFA9);
		c=HH(c,d,a,b,x[k+7], S33,0xF6BB4B60);
		b=HH(b,c,d,a,x[k+10],S34,0xBEBFBC70);
		a=HH(a,b,c,d,x[k+13],S31,0x289B7EC6);
		d=HH(d,a,b,c,x[k+0], S32,0xEAA127FA);
		c=HH(c,d,a,b,x[k+3], S33,0xD4EF3085);
		b=HH(b,c,d,a,x[k+6], S34,0x4881D05);
		a=HH(a,b,c,d,x[k+9], S31,0xD9D4D039);
		d=HH(d,a,b,c,x[k+12],S32,0xE6DB99E5);
		c=HH(c,d,a,b,x[k+15],S33,0x1FA27CF8);
		b=HH(b,c,d,a,x[k+2], S34,0xC4AC5665);
		a=II(a,b,c,d,x[k+0], S41,0xF4292244);
		d=II(d,a,b,c,x[k+7], S42,0x432AFF97);
		c=II(c,d,a,b,x[k+14],S43,0xAB9423A7);
		b=II(b,c,d,a,x[k+5], S44,0xFC93A039);
		a=II(a,b,c,d,x[k+12],S41,0x655B59C3);
		d=II(d,a,b,c,x[k+3], S42,0x8F0CCC92);
		c=II(c,d,a,b,x[k+10],S43,0xFFEFF47D);
		b=II(b,c,d,a,x[k+1], S44,0x85845DD1);
		a=II(a,b,c,d,x[k+8], S41,0x6FA87E4F);
		d=II(d,a,b,c,x[k+15],S42,0xFE2CE6E0);
		c=II(c,d,a,b,x[k+6], S43,0xA3014314);
		b=II(b,c,d,a,x[k+13],S44,0x4E0811A1);
		a=II(a,b,c,d,x[k+4], S41,0xF7537E82);
		d=II(d,a,b,c,x[k+11],S42,0xBD3AF235);
		c=II(c,d,a,b,x[k+2], S43,0x2AD7D2BB);
		b=II(b,c,d,a,x[k+9], S44,0xEB86D391);
		a=AddUnsigned(a,AA); b=AddUnsigned(b,BB); c=AddUnsigned(c,CC); d=AddUnsigned(d,DD);
	}
	// Step 5.  Output the 128 bit digest
	var temp= WordToHex(a)+WordToHex(b)+WordToHex(c)+WordToHex(d);
	return temp.toLowerCase();
}

/********************************
 * FUNCTION:	host_info
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.02.12
 * DESCRIPTION:	Use this function to get browser info.  It only loads the info the first time; subsequent calls simply
 *              return the previously-generated object.
 * PARAMETERS:	none
 * RETURNS:	    object: major_ver, minor_ver, name properties
 */
goroam.browser.host_info = function() {
  if (!goroam.browser.__host_info) goroam.browser.load_host_info();
  return goroam.browser.__host_info;
}

/********************************
 * FUNCTION:	load_host_info
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.02.12
 * DESCRIPTION:	This function loads browser information about the viewer's browser.
 * PARAMETERS:	none
 * RETURNS:	    none
 */
goroam.browser.load_host_info = function() {
  goroam.browser.__host_info = {major_ver: 0, minor_ver: 0, name: ''};
  var userAgent=window.navigator.userAgent.toLowerCase();
  var index;
  var version=null;
  if((index=userAgent.indexOf('msie'))>=0) {
    goroam.browser.__host_info.name = goroam.enm.browsers.ie;
    version = userAgent.substr(index+5);
  }
  else if((index=userAgent.indexOf('firefox'))>=0) {
    goroam.browser.__host_info.name = goroam.enm.browsers.mozilla;
    version=userAgent.substr(index+8);
  }
  else if((index=userAgent.indexOf('safari'))>=0){
    goroam.browser.__host_info.name = goroam.enm.browsers.safari;
    version=userAgent.substr(index+7);
  }
  else if((index=userAgent.indexOf('gecko'))>=0){
    goroam.browser.__host_info.name = goroam.enm.browsers.mozilla;
    version=window.navigator.appVersion;
  }
  else{
    goroam.browser.__host_info.name = goroam.enm.browsers.other;
  }
  if(version){
    var verFloat=parseFloat(version);
    goroam.browser.__host_info.major_ver = parseInt(verFloat);
    if((index=version.indexOf('.'))>=0) {
      goroam.browser.__host_info.minor_ver = parseInt(version.substr(index+1));
    }
  }
}

// A function to add an item to an array only if not already in the array.
Array.prototype.add_if_new = function(obj) {
	return goroam.array.add_if_new(this, obj);
};
// A function to find the index of an item in an array.  Returns -1 if not found.
Array.prototype.Find = function(val) {
	return goroam.array.in_array(this, val);
};
// A function to remove members of the array that are in both this array and the given
// array.  This also removes members from the second array.
Array.prototype.remove_intersection = function(arr) {
	return goroam.array.remove_intersection(this, arr);
};

// This function adds a new item to an array only if the item is not already in the
// array.  Returns true if added, false if not added (already present).
goroam.array.add_if_new = function(arr, obj) {
	var bolResult = false;
	if (goroam.array.in_array(arr, obj) < 0) {
		arr.push(obj);
		bolResult = true;
	}
	return bolResult;
}
// Searches the array for the given value/object.  If not found, returns -1.
goroam.array.in_array = function(arr, val) {
	var i, iResult = -1;
	for (i = 0; i < arr.length; i++) {
		if (arr[i] == val) break;
	}
	// If the item is not found, i will be past the end of the array.
	if (i != arr.length) iResult = i;
	return iResult;
}
// A function to remove array members that appear in both arrays.
goroam.array.remove_intersection = function(arr1, arr2) {
	var arrResult = [];
	for (var i = arr1.length - 1; i >= 0; i--) {
		for (var j = 0; j < arr2.length; j++) {
			if (arr1[i] == arr2[j]) {
				arr1.splice(i, 1);
				arrResult.unshift(arr2.splice(j, 1));
				break;
			}
		}
	}
	return arrResult;
}

/********************************
 * OBJECT:	compass_position
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2006.05.14
 * DESCRIPTION:	This object can be loaded to figure out the x any y multipliers to position
 *		something in a cardinal direction from another item.
 * PARAMETERS:	strPosition	Any cardinal direction abbreviation, or c (for center).
 * RETURNS:	The CompassPosition object
 */

goroam.math.compass_position = function(strPosition, strOrientation) {
	var _self = this; 				// Save a reference to the object for later, to prevent confusion with the event 'this' object.
	_self.__position = '';
	_self.__orientation = '';
	_self.x = 0;
	_self.y = 0;
	_self.set_position(strPosition, strOrientation);
};
// A function to figure out the x-coordinate of the compass position edge, based on
// a given x-coord and the width of the object. Both are required.
goroam.math.compass_position.prototype.get_edge_left = function(iX, iWidth) {
	return iX + (this.x * (iWidth == 0 ? 0 : iWidth / 2));
};
// A function to figure out the y-coordinate of the compass position edge, based on
// a given y-coord and the height of the object. Both are required.
goroam.math.compass_position.prototype.get_edge_top = function(iY, iHeight) {
	return iY + (this.y * (iHeight == 0 ? 0 : iHeight / 2));
};
// A function to figure out the x-coordinate of the left of the object, based on
// a given x-coord and the width of the object. Both are required.
goroam.math.compass_position.prototype.get_left = function(iX, iWidth) {
	// 0 should center (thus the -0.5) and 1 needs to take width into account (thus the 0).
	return iX + (iWidth * (this.x == 0 ? -0.5 : (this.x == 1 ? 0 : this.x)));
};
// A function to figure out the y-coordinate of the top of the object, based on
// a given y-coord and the height of the object. Both are required.
goroam.math.compass_position.prototype.get_top = function(iY, iHeight) {
	// 0 should center (thus the -0.5) and 1 needs to take height into account (thus the 0).
	return iY + (iHeight * (this.y == 0 ? -0.5 : (this.y == 1 ? 0 : this.y)));
};
// A function to switch between browser orientation (which starts upper left corner
// and positive x is right and positive y is down) or normal orientation (which starts
// lower left corner and positive x is left and positive y is up).  Defaults to
// browser positioning.
goroam.math.compass_position.prototype.set_orientation = function(strOrientation) {
	var _self = this;
	strOrientation = strOrientation || '';
	strOrientation = strOrientation.toLowerCase();
	// Only accept browser or normal.
	if ((strOrientation == 'browser') || (strOrientation == 'normal')) {
		// Only change the values is the orientation is different.
		if (_self.__orientation != strOrientation) {
			_self.__orientation = strOrientation;
			_self.y *= -1
		}
	}
}
// A function to set the position.
goroam.math.compass_position.prototype.set_position = function(strPosition, strOrientation) {
	var _self = this;
	// Default to empty string and make it lower case.
	strPosition = strPosition || '';
	strPosition = strPosition.toLowerCase();
	strOrientation = strOrientation || '';
	strOrientation = strOrientation.toLowerCase();
	_self.__position = strPosition;
	switch (_self.__position) {
		case 'n':
			_self.x = 0;
			_self.y = -1;
			break;
		case 'ne':
			_self.x = 1;
			_self.y = -1;
			break;
		case 'e':
			_self.x = 1;
			_self.y = 0;
			break;
		case 'se':
			_self.x = 1;
			_self.y = 1;
			break;
		case 's':
			_self.x = 0;
			_self.y = 1;
			break;
		case 'sw':
			_self.x = -1;
			_self.y = 1;
			break;
		case 'w':
			_self.x = -1;
			_self.y = 0;
			break;
		case 'nw':
			_self.x = -1;
			_self.y = -1;
			break;
		default:
			_self.x = 0;
			_self.y = 0;
			break;
	}
	// If the orientation is empty, use the current orientation; if the orientation
	// is given, set it to that value.
	strOrientation = (strOrientation == '') ? _self.__orientation : strOrientation;
	// The switch above is based on a 'browser' orientation. Reset those values
	// based on the given orientation.
	_self.__orientation = 'browser';
	_self.set_orientation(strOrientation);
}
// A function to print out the values.
goroam.math.compass_position.prototype.toString = function() {
	return '(' + this.x + ', ' + this.y + ')';
}



/********************************
 * FUNCTION:		random_string
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.10.14
 * DESCRIPTION:	This function produces a string of random characters.
 * PARAMETERS:	iLength		Optional. The number of characters to include in the resultant string.  Defaults to 10.
 * RETURNS:		  The random string.
 */
goroam.math.random_string = function(iLength) {
	var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
	iLength = (iLength || 10);
	var strResult = '';
	for (var i=0; i < iLength; i++) {
		var rnum = Math.floor(Math.random() * chars.length);
		strResult += chars.substring(rnum,rnum+1);
	}
	return strResult;
}


/********************************
 * OBJECT:	request
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2006.04.29
 * DESCRIPTION:	This object loads an XMLHttpRequest object, to use when requesting from or
 *		posting to a URL.  It loads the object based on the browser settings and
 *		also adds a timeout feature.
 * PARAMETERS:	None
 * RETURNS:	The xml_http object
 */
goroam.http.request = function() {
  // Save a reference to the object for later, to prevent confusion with the event 'this' object.
	var self = this;
  // The main XMLHttpRequest object, loaded differently based on browser.
	var objHttp;
  // Attempt to load the object, and keep track of the success of the loading.
	var bolIsLoaded = load_object();
	var bolIsAsync = false;
  // A flag to show whether or not the Http requested has finished.
	this.finished = false;
  // The default timeout setting.
	this.timeout = 60;
  // The timeout id returned by window.setTimeout, when the Http request is caled asynchronously.
	this.timeout_id = undefined;
  // The function to call when the URL completes loading.
	this.return_func = undefined;

	// Public properties - I couldn't figure out how to expose the XMLHttpRequest object's properties,
  // so I created these get and set methods.
	this.get = function(strProperty) {
		return objHttp[strProperty];
	};
	this.set = function(strProperty, strValue) {
		return objHttp[strProperty] = strValue;
	};

	// Public functions - exposing the objects existing functions.  I only allow calling these
  // functions if the XMLHttpRequest object loaded properly
	this.abort = function() {
		if (bolIsLoaded) return objHttp.abort();
	};
	this.get_all_response_headers = function() {
		if (bolIsLoaded)
			return objHttp.getAllResponseHeaders();
		else
			return '';
	};
	this.get_response_header = function(str) {
		if (bolIsLoaded)
			return objHttp.getResponseHeader(str);
		else
			return '';
	};
	this.open = function(strMethod, strURL, bolAsync, strUsername, strPassword) {
		if (bolIsLoaded) {
			bolIsAsync = bolAsync;
			return objHttp.open(strMethod, strURL, bolAsync, strUsername, strPassword);
		}
	};
	this.send = function(str) {
		// The timeout logic here only matters for asynchronous URL calls.
		if (bolIsLoaded) {
			var strTemp = (str || '');
			if (bolIsAsync) {
				self.finished = false;
				// If the return function needs to be called or the timeout should be detected, call the goroam.http.check
        // function when the ready state changes.  In this case, pass in the Http object so it doesn't rely upon the
        // window.TimeoutObject (which will not be set if there is no timeout set.
				if ((self.return_func != undefined) || (self.timeout > 0)) {
					objHttp.onreadystatechange = function() {
						return goroam.http.check(self, false);
					};
				}
				// If the timeout is set, set up a call to goroam.http.check when the timeout expires.  If the URL call
        // completes before the timeout, the timeout gets canceled; if the URL doesn't complete before the timeout,
        // it will abort the URL call and then cancel the timeout.
				if (self.timeout > 0) {
					// Get a reference to the goroam.http.check function, but feed it the parameters ahead of time so it can
          // be called without them later.
					var fTimeout = goroam.http.check_later(self, true);
					self.timeout_id = window.setTimeout(fTimeout, 1000 * self.timeout);
					objHttp.send(str);
					// Kill the function reference to help with garbage collection in IE.
					fTimeout = null;
				} else {
					objHttp.send(str);
				}
			} else {
				objHttp.send(str);
			}
		} else {
			// If not properly loaded, call the return function as a failure.
			if (self.return_func != undefined) self.return_func(false, '');
		}
	};
	this.set_request_header = function(strLabel, strValue) {
		if (bolIsLoaded) return objHttp.setRequestHeader(strLabel, strValue);
	};



	function load_object(url) {
		var bolResult = false;
		// branch for native XMLHttpRequest object
		if(window.XMLHttpRequest) {
			try {
				objHttp = new XMLHttpRequest();
				bolResult = true;
			} catch(e) {}
		// branch for IE/Windows ActiveX version
		} else if(window.ActiveXObject) {
			try {
				objHttp = new ActiveXObject("Msxml2.XMLHTTP");
				bolResult = true;
			} catch(e) {
				try {
					objHttp = new ActiveXObject("Microsoft.XMLHTTP");
					bolResult = true;
				} catch(e) {}
			}
		}
		return bolResult;
	}
}



/********************************
 * FUNCTION:	check
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2006.04.29
 * DESCRIPTION:	This object checks the completion of a URL call through the http request object.
 *		It looks for successful completion or a timeout, calls the return_func function,
 *		and cleans up all timeout settings.  This function goes with the http request
 *		object defined above.
 * PARAMETERS:	objHttp		The http request object to reference.
 *		bolFromTimeout	true if the function is called from a timeout event, false if not.
 * RETURNS:	None
 */
goroam.http.check = function(objHttp, bolFromTimeout) {
	var bolResult = false;
	var strResponseText = '';
	if (objHttp != undefined) {
		if (!objHttp.finished) {
			if (objHttp.get('readyState') == 4) {
				if (objHttp.get('status') == 200) {
					// Only successful if the readyState = 4 and the status = 200.
					bolResult = true;
				}
			}
			if (bolFromTimeout || bolResult) {
				objHttp.finished = true;
				// Only get the resonseText if successful (otherwise there's an error).
				if (bolResult) strResponseText = objHttp.get('responseText');
				// Call the return_func function if one has been specified.
				if (objHttp.return_func != undefined) objHttp.return_func(bolResult, strResponseText);
				// Clean up timeout settings.
				if (objHttp.timeout_id != undefined) {
					window.clearTimeout(objHttp.timeout_id);
				}
				// Abort the URL call if a timeout and the URL hasn't finished yet.
				if (!bolResult) objHttp.abort();
			}
		}
	}
}



// Use this function to create a reference to the function above, but with the
// given parameters.  Useful for when calling the function from setTimeout
goroam.http.check_later = function(obj, bolFromTimeout) {
	return (function() {
		goroam.http.check(obj, bolFromTimeout);
	});
}

goroam.controls.__popup_map_id = 'gr_popup_map';



/********************************
 * FUNCTION:	close_popup
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.03.03
 * DESCRIPTION:	This function closes the on-screen popup.
 * PARAMETERS:	none
 * RETURNS:	    none
 */
goroam.controls.close_popup = function() {
  if (goroam.controls.__popup_container) {
    goroam.controls.__popup_container.style.display = 'none';
    goroam.controls.__popup_div.className = 'gr_popup_div';
    while (goroam.controls.__popup_text.childNodes.length > 0) goroam.controls.__popup_text.removeChild(goroam.controls.__popup_text.firstChild);
  }
}



/********************************
 * FUNCTION:	create_popup_wrapper
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.03.03
 * DESCRIPTION:	This function creates the wrapper for an on-screen popup, if necessary.  It creates the background
 *              obscurer and the close button.
 * PARAMETERS:	none
 * RETURNS:	    none
 */
goroam.controls.create_popup_wrapper = function() {
  if (!goroam.controls.__popup_container) {
    var elDiv = document.createElement('div');
    elDiv.style.height = '100%';
    elDiv.style.left = '0px';
    elDiv.style.position = 'absolute';
    elDiv.style.top = '0px';
    elDiv.style.width = '100%';
    elDiv.style.zoom = 1;
    goroam.dhtml.append_to_body(elDiv);
    goroam.controls.__popup_container = elDiv;
  }
  if (!goroam.controls.__popup_obscurer) {
    var elDiv = document.createElement('div');
    elDiv.className = 'gr_popup_background';
    goroam.controls.__popup_container.appendChild(elDiv);
    goroam.controls.__popup_obscurer = elDiv;
    goroam.event.bindDom(goroam.controls.__popup_obscurer, 'click', goroam.controls.__popup_obscurer, goroam.controls.close_popup);
  }
  if (!goroam.controls.__popup_div) {
    var elDiv = document.createElement('div');
    elDiv.className = 'gr_popup_div';
    goroam.controls.__popup_container.appendChild(elDiv);
    goroam.controls.__popup_div = elDiv;
  }
  if (!goroam.controls.__popup_text) {
    var elDiv = document.createElement('div');
    elDiv.className = 'gr_popup_text';
    goroam.controls.__popup_div.appendChild(elDiv);
    goroam.controls.__popup_text = elDiv;
  }
  if (!goroam.controls.__popup_close_button) {
    var elClose = document.createElement('div');
    elClose.className = 'gr_popup_close_button';
    elClose.title = 'Close';
    goroam.controls.__popup_div.appendChild(elClose);
    goroam.controls.__popup_close_button = elClose;
    goroam.event.bindDom(goroam.controls.__popup_close_button, 'click', goroam.controls.__popup_close_button, goroam.controls.close_popup);
  }
  var elBody = goroam.dhtml.get_body();
  if (elBody && elBody.scrollHeight) {
    // I had to do this here because I was having difficulty getting it to work from CSS.
    // This works correctly in FF, IE, Chrome, and Safari. The width doesn't work correctly in Opera.
		var iHeight = Math.max(elBody.scrollHeight, elBody.clientHeight);
		var iWidth = Math.max(elBody.scrollWidth, elBody.clientWidth);
	  goroam.controls.__popup_obscurer.style.height = iHeight + 'px';
  	goroam.controls.__popup_obscurer.style.width = iWidth + 'px';
    goroam.controls.__popup_div.style.top = ((window.pageYOffset || elBody.scrollTop || document.documentElement.scrollTop) + 10) + 'px';
  }
  goroam.controls.__popup_container.style.display = 'block';
}



/********************************
 * FUNCTION:	popup
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.03.03
 * DESCRIPTION:	This function creates a popup window within the current screen.
 * PARAMETERS:	strText       The text to show in the popup.
 *              strClassName  Optional.  A class name to apply to the popup, for styling.
 * RETURNS:	    none
 */
goroam.controls.popup = function(strText, strClassName) {
  goroam.controls.create_popup_wrapper();
  goroam.controls.__popup_text.innerHTML = strText;
  goroam.dhtml.edit_class_name(goroam.controls.__popup_div, 'msg_box' + (strClassName ? ' ' + strClassName : ''));
}



/********************************
 * FUNCTION:	popup_map
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.03.04
 * DESCRIPTION:	This function creates a popup window containing a map within the current screen.
 * PARAMETERS:	objLatLng     The lat/lng point to show on the map.
 *              iZoom         Optional.  The zoom level to use when showing the lat/lng.  Defaults to a constant.
 * RETURNS:	    none
 */
goroam.controls.popup_map = function(objLatLng, iZoom) {
  goroam.controls.create_popup_wrapper();
  if (!goroam.controls.__popup_map) {
    var elDiv = document.createElement('div');
    elDiv.id = goroam.controls.__popup_map_id;
    goroam.controls.__popup_map = elDiv;
  }
  goroam.dhtml.edit_class_name(goroam.controls.__popup_div, 'popup_map');
  goroam.controls.__popup_text.appendChild(goroam.controls.__popup_map);
  var objMap = goroam.google.maps.get_map(goroam.controls.__popup_map_id);
  objMap.recenter_active_marker(objLatLng, (iZoom || parseInt('16')));
  objMap.checkResize();
}



/********************************
 * FUNCTION:	popup_window
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.02.19
 * DESCRIPTION:	This function creates a popup window for a given URL.
 * PARAMETERS:	strUrl        The URL to open in the popup.
 *              iHeight       Optional.  The height of the window.  Defaults to 200px.
 *              iWidth        Optional.  The width of the window.  Defaults to 300px.
 *              strWindowName Optional.  The name of the window to open in.  Use this to open multiple different windows.
 *              bolShowMenus  Optional.  True to show normal window menus (location, menu, back, etc.); False to hide those things.  Defaults to false.
 *              bolResizable  Optional.  True to make the window resizable; False to make the window static.  Defaults to false.
 * RETURNS:	    A handle for the window that is opened.
 */
goroam.controls.popup_window = function(strUrl, iHeight, iWidth, strWindowName, bolShowMenus, bolResizable) {
  strWindowName = (strWindowName || '');
  var iX = 0, iY = 0;
  // Figure out the dimensions and location of the popup window.
  if (window.screen) {
    if (!iHeight) iHeight = .9 * window.screen.height;
    if (!iWidth) iWidth = .9 * window.screen.width;
    iY = (window.screen.height / 2) - (iHeight / 2);
    iX = (window.screen.width / 2) - (iWidth / 2);
  } else {
    if (!iHeight) iHeight = 300;
    if (!iWidth) iWidth = 200;
  }
  var strOptions = '';
  if (bolShowMenus) {
    strOptions += 'location=yes,directories=yes,menubar=yes';
  } else {
    strOptions += 'location=no,directories=no,menubar=no';
  }
  if (bolResizable) {
    strOptions += ',resizable=yes,scrollbars=yes';
  } else {
    strOptions += ',resizable=no,scrollbars=no';
  }
  strOptions += ',status=yes,titlebar=yes,toolbar=no,height=' + iHeight + 'px,width=' + iWidth + 'px,screenX=' + iX + ',screenY=' + iY + ',left=' + iX + ',top=' + iY;
  return window.open(strUrl, strWindowName, strOptions);
}


goroam.google.maps.version = '2.176';
if ((goroam.browser.host_info().name == goroam.enm.browsers.safari) && (goroam.browser.host_info().major_ver < 525)) goroam.google.maps.version = '';

if (goroam.google.maps.version != '') {
	google.load('maps', goroam.google.maps.version);
} else {
	alert('GoRoam Error: Sorry, your browser is not supported by the maps on this page');
}

// Load local variables for functions.
goroam.google.maps.__active_map = undefined;
goroam.google.maps.__active_map_id = goroam.settings.map;
goroam.google.maps.__count_of_page_lat_lng = 0;
goroam.google.maps.__count_of_processing_addr = 0;
goroam.google.maps.__page_maps = {};
goroam.google.maps.__addr_attr = 'addr';
goroam.google.maps.__lat_attr = 'lat';
goroam.google.maps.__lng_attr = 'lng';
goroam.google.maps.__map_attr = 'map';

// I added this because the real version uses event listeners, which are called in
// opposite directions in IE vs Moz (FIFO vs FILO).  I added this so that it would
// have the same functionality, but would work as FIFO in all browsers.
google.__setOnLoadCallback = google.setOnLoadCallback;
google.__onload_callback_functions = [];
google.__onload_callback = function(e) {
	for (var i = 0; i < google.__onload_callback_functions.length; i++) {
		google.__onload_callback_functions[i](e);
	}
};
google.__setOnLoadCallback(google.__onload_callback);
google.setOnLoadCallback = function(fFunc) { google.__onload_callback_functions.add_if_new(fFunc); };


/********************************
 * FUNCTION:	extend_functionality
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.01.25
 * DESCRIPTION:	This function extends the out-of-the-box Google Maps objects.
 * PARAMETERS:	none
 * RETURNS:		  none
 */
goroam.google.maps.extend_functionality = function() {
  // Map2 object extensions
  // New "local" variables
  google.maps.Map2.prototype.active_marker = undefined;
  google.maps.Map2.prototype.__bounds = undefined;
  google.maps.Map2.prototype.__lines = undefined;

  // New methods
  google.maps.Map2.prototype.add_lat_lng = function(dLat, dLng, strAnchor, strAnchorText) {
    var strKey = dLat + '_' + dLng;
    if (!this.__lat_lng) this.__lat_lng = {};
    if (this.__lat_lng[strKey]) {
      // Do something here for multiple hits on the same lat/lng.
    } else {
      var objLatLng = new google.maps.LatLng(dLat, dLng);
      var objMarker = goroam.google.maps.create_marker(objLatLng);
      this.__lat_lng[strKey] = objMarker;
      this.addOverlay(this.__lat_lng[strKey]);
      this.get_bounds().extend(objLatLng);
    }
		// Add menu items if the anchor is given.
		strAnchor = (strAnchor || '').trim();
		strAnchorText = (strAnchorText || '').trim();
		if ((strAnchor != '') && (strAnchorText != '')) {
			var objMarker = this.__lat_lng[strKey];
			if (!objMarker.__menu) {
				objMarker.__menu = new goroam.google.maps.html_overlay({ class_name: 'gr_point_menu', container: document.createElement('ul'), lat_lng: objLatLng, position: 'se'});
				this.addOverlay(objMarker.__menu);
				goroam.event.bind(objMarker, 'click', objMarker, objMarker.trigger_hide_menus);
				goroam.event.bind(objMarker, 'click', objMarker.__menu.get_container(), goroam.dhtml.toggle_me);
				goroam.event.bind(this.getContainer(), 'hide_menus', objMarker, objMarker.hide_menu);
			}
			var elItem = document.createElement('li');
			var elAnchor = document.createElement('a');
			elAnchor.href = '#' + strAnchor;
			elAnchor.innerHTML = strAnchorText;
			goroam.dhtml.edit_class_name(elItem, 'point_menu_item');
			elItem.appendChild(elAnchor);
			objMarker.__menu.get_container().appendChild(elItem);
			if ((objMarker.__menu.get_container().childNodes.length % 2) == 0) goroam.dhtml.edit_class_name(elItem, 'odd_row');
		}
    // Return the object that was created.
    return this.__lat_lng[strKey];
  }

  google.maps.Map2.prototype.__addOverlay = google.maps.Map2.prototype.addOverlay;
  google.maps.Map2.prototype.addOverlay = function(objOverlay) {
    this.__addOverlay(objOverlay);
    objOverlay.map = this;
  }
  google.maps.Map2.prototype.__removeOverlay = google.maps.Map2.prototype.removeOverlay;
  google.maps.Map2.prototype.removeOverlay = function(objOverlay) {
    this.__removeOverlay(objOverlay);
    objOverlay.map = undefined;
  }

	// This function adds a directions line to the map.  If the line is not ready, it will try again.
	google.maps.Map2.prototype.add_directions_line = function(objDirections, strColor, iWidth, dOpacity, iTravelMode) {
		goroam.google.maps.add_directions_line_to_map(this, objDirections, strColor, iWidth, dOpacity, iTravelMode);
	}

	// This function adds a point to the line on the map.  If the line does not already exist, it will be created.
	google.maps.Map2.prototype.add_point_to_line = function(strLineId, objLatLng, iIndex, strRouteType, strColor, iWidth, dOpacity) {
		goroam.google.maps.add_point_to_line(this, strLineId, objLatLng, iIndex, strRouteType, strColor, iWidth, dOpacity);
	}

  // This function creates a marker in the given lat/lng, or at the center of the map if one is not given.
  // Use the bolDraggable flag to make the marker draggable.
  google.maps.Map2.prototype.create_active_marker = function(objLatLng, bolDraggable) {
    objLatLng = (objLatLng || this.getCenter());
    this.active_marker = goroam.google.maps.create_marker(objLatLng, {draggable: true});
    this.addOverlay(this.active_marker);
    goroam.event.addListener(this.active_marker, 'dragend', function(objLatLng) { goroam.event.trigger(this.map, 'location_found', objLatLng); });
  }

	// This function extends the map bounds to include the given LatLng.
	google.maps.Map2.prototype.extend_bounds = function(objLatLng) {
		this.get_bounds().extend(objLatLng);
		this.resize_to_bounds();
	}

	// This function gets the map's internal bounds object.
	google.maps.Map2.prototype.get_bounds = function() {
		if (!this.__bounds) this.__bounds = new google.maps.LatLngBounds();
		return this.__bounds;
	}

	// This function gets the array of lines already on the map.
	google.maps.Map2.prototype.get_line_array = function() {
		if (!this.__lines) this.__lines = [];
		return this.__lines;
	}

  // This function searched for the given location and recenters the map on that location if found.
  google.maps.Map2.prototype.goto_location = function(strLocation) {
    goroam.google.maps.find_location(this, strLocation, function(objPoint, strAddress, objPlacemark) {
      if (objPlacemark && objPlacemark.ExtendedData && objPlacemark.ExtendedData.LatLonBox) {
        this.recenter_active_marker(objPoint, new google.maps.LatLngBounds(new google.maps.LatLng(objPlacemark.ExtendedData.LatLonBox.south, objPlacemark.ExtendedData.LatLonBox.west), new google.maps.LatLng(objPlacemark.ExtendedData.LatLonBox.north, objPlacemark.ExtendedData.LatLonBox.east)));
      } else {
        this.recenter_active_marker(objPoint);
      }
      goroam.event.trigger(this, 'location_found', objPoint, strAddress, objPlacemark); });
  }

  // This function moves the active marker to a given lat/lng and recenters/resizes the map on that point.
  // objZoom can be either a zoom level (int) or a GLatLngBounds object.
  google.maps.Map2.prototype.recenter_active_marker = function(objLatLng, objZoom) {
    if (this.active_marker) {
      // Change the lat/lng of the marker if it already exists.
      this.active_marker.setLatLng(objLatLng);
    } else {
      // Create the marker if it does not already exist.
      this.create_active_marker(objLatLng, false);
    }
    // Recenter the map on the moved marker.
    if (goroam.system.is_numeric(objZoom) || (objZoom == undefined)) {
      this.setCenter(objLatLng, objZoom);
    } else {
      this.resize_to_bounds(objZoom);
    }
  }
	// A function to turn on recentering of the active marker when the given event is triggered.
	google.maps.Map2.prototype.recenter_active_marker_on_event = function(strEvent) {
		strEvent = strEvent.toLowerCase();
		if (strEvent == 'dblclick') this.disableDoubleClickZoom();
		google.maps.Event.addListener(this, strEvent, function(objOverlay, objLatLng) { this.recenter_active_marker(objLatLng); });
	}
  
  // This function resizes and recenters the map based on either a) the optional bounds object or b) the map's
  // internal bounds object.  Returns true upon success.
  google.maps.Map2.prototype.resize_to_bounds = function(objLatLngBounds) {
    var bolResult = false;
    // Default to resizing to the map's bounds.
    objLatLngBounds = (objLatLngBounds || this.get_bounds());
    if (objLatLngBounds && !objLatLngBounds.isEmpty()) {
      var iZoom = this.getBoundsZoomLevel(objLatLngBounds);
      this.setCenter(objLatLngBounds.getCenter(), iZoom);
    }
    return bolResult;
  }



  // This function shows a focus_overlay at the given lat/lng point.
  google.maps.Map2.prototype.show_focus = function(objLatLng) {
    if (!this.__focus_overlay) {
      this.__focus_overlay = goroam.google.maps.create_focus_overlay(objLatLng);
      this.addOverlay(this.__focus_overlay);
    } else {
      if (this.__focus_overlay.map != this) this.addOverlay(this.__focus_overlay);
      this.__focus_overlay.setLatLng(objLatLng);
    }
  }



  // These functions show/hide a GLayer on the map.
  google.maps.Map2.prototype.show_layer = function(strId) {
    if (!this.__layers) this.__layers = {};
    if (!this.__layers[strId]) {
      this.__layers[strId] = new google.maps.Layer(strId);
      this.addOverlay(this.__layers[strId]);
    }
    this.__layers[strId].show();
  }
  google.maps.Map2.prototype.hide_layer = function(strId) {
    if (!this.__layers) this.__layers = {};
    if (this.__layers[strId]) this.__layers[strId].hide();
  }



  // This function triggers the 'hide_menus' event for the map.
  google.maps.Marker.prototype.trigger_hide_menus = function() {
		if (this.map) goroam.event.trigger(this.map.getContainer(), 'hide_menus', this);
  }



  // This function hides a menu for the current marker.
  google.maps.Marker.prototype.hide_menu = function(objTarget) {
		if (this.__menu && (this != objTarget)) goroam.dhtml.hide(this.__menu.get_container());
  }



  /********************************
   * OBJECT:	html_overlay
   * WRITTEN BY:	Ryan Martin
   * WRITTEN ON:	2006.05.13
   * DESCRIPTION:	This object is used to place any HTML onto a Google Map at a given
   *		latitude/longitude.
   * BROWSERS:	IE		Good
   *		Firefox		Good
   *		Netscape	Good
   * PARAMETERS:	arrInit		A named map array of init values (as follows:)
   *					class_name	Optional. A className to assign to the container for CSS.
   *					container	Optional. The HTML element to place on the map. If not given, a new DIV is created.
   *					lat_lng		A LatLng object, at which the HTML should be located.
  external to the object.
   *					position	Optional. The compass_position of the object relative to the lat_lng. Defaults to centered.
   * RETURNS:	The GHTML object
   */

  goroam.google.maps.html_overlay = function(arrInit) {
    var _self = this;
    // Create the container for the object.
    if (arrInit && arrInit.container) {
      _self.__container = arrInit.container;
    } else {
      _self.__container = document.createElement('div');
    }
    // Make sure the container has a default className and any given className.
    goroam.dhtml.edit_class_name(_self.__container, 'gr_html_overlay' + ((arrInit && arrInit.class_name) ? ' ' + arrInit.class_name : ''), '', true);
    // Set the LatLng
    if (arrInit && arrInit.lat_lng) _self.__lat_lng = arrInit.lat_lng;
    // Set the position; defaults to empty string.
    _self.__position = (arrInit && arrInit.position) ? arrInit.position : '';
  };
  // The object is based off of empty GOverlay class.
  goroam.google.maps.html_overlay.prototype = new google.maps.Overlay();
  // Copy the html_overlay into a new object.
  goroam.google.maps.html_overlay.prototype.copy = function() {
	  var _self = this;
	  var objCopy = new goroam.google.maps.html_overlay({container: _self.__container.cloneNode(true), lat_lng: _self.__lat_lng, position: _self.__position});
	  // Create a copy of the object with the new unique ID.
	  return objCopy;
  }
  // A method used to get the overlay's HTML container.
  goroam.google.maps.html_overlay.prototype.get_container = function() {
    return this.__container;
  }
  // A method used to get the overlay's lat/lng point.
  goroam.google.maps.html_overlay.prototype.getLatLng = function() {
    return this.__lat_lng;
  }
  // Initialize the html_overlay object by adding it to the map.
  goroam.google.maps.html_overlay.prototype.initialize = function(objMap) {
	  var _self = this;
	  _self.__container.style.position = 'absolute';
	  _self.map = objMap;
	  _self.map.getPane(G_MAP_MARKER_PANE).appendChild(_self.__container);
  };
  // Move the html_overlay to a new point on the map.
  goroam.google.maps.html_overlay.prototype.setLatLng = function(objLatLng) {
	  var _self = this;
	  var bolResult = false;
	  if (objLatLng) {
		  _self.__lat_lng = objLatLng;
		  _self.redraw(true);
		  bolResult = true;
	  }
	  return bolResult;
  };
  // Redraw the HTML node based on the current projection and zoom level.
  goroam.google.maps.html_overlay.prototype.redraw = function(force) {
	  var _self = this;
	  // We only need to redraw if the coordinate system has changed
	  if (!force || !(_self.map)) return;
	  // Calculate the DIV location based on the map location and zoom level.
	  var objCenter = _self.map.fromLatLngToDivPixel(_self.__lat_lng);

	  // Now position our DIV based on the map's DIV coordinates.
	  var objPosition = new goroam.math.compass_position(_self.__position);
	  _self.__container.style.left = objPosition.get_left(objCenter.x, _self.__container.offsetWidth) + 'px';
	  _self.__container.style.top = objPosition.get_top(objCenter.y, _self.__container.offsetHeight) + 'px';
  };
  // Remove the html_overlay from the map pane
  goroam.google.maps.html_overlay.prototype.remove = function() {
    var elContainer = this.__container;
    elContainer.parentNode.removeChild(elContainer);
  };
}



/********************************
 * FUNCTION:	add_directions_line_to_map
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.10.18
 * DESCRIPTION:	This function adds a directions line to the map if it is ready.  If the directions are updated, the line will also be updated,
 *							so there is no need to re-add the line after the first time.
 * PARAMETERS:	objMap				The Map to add the line to.
 *							objDirections	The GDirections object to check for the Polyline.
 * RETURNS:			none
 */
goroam.google.maps.add_directions_line_to_map = function(objMap, objDirections, strColor, iWidth, dOpacity, iTravelMode) {
	var objStatus = objDirections.getStatus();
	if (objStatus && objStatus.code == 200) {
		var objLine = objDirections.getPolyline();
		var objOptions = {};
		objMap.addOverlay(objLine);
		if (strColor) objOptions.color = strColor;
		if (iWidth) objOptions.weight = iWidth;
		if (dOpacity) objOptions.opacity = dOpacity;
		objLine.setStrokeStyle(objOptions);
		//objMap.
	} else if (!objStatus || (objStatus.code == 500)) {
		window.setTimeout(function() { goroam.google.maps.add_directions_line_to_map(objMap, objDirections, strColor, iWidth, dOpacity); }, 100);
	} else if (objStatus && (objStatus.code == 620)) {
		// If the error is due to too many requests, attempt loading it again.
		objDirections.loadFromWaypoints(objDirections._waypoints, { getPolyline: true, preserveViewport: true, travelMode: (iTravelMode || G_TRAVEL_MODE_DRIVING) });
		window.setTimeout(function() { goroam.google.maps.add_directions_line_to_map(objMap, objDirections, strColor, iWidth, dOpacity); }, 100);
	}
}



/********************************
 * FUNCTION:	add_point_to_line
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.10.13
 * DESCRIPTION:	This function adds a point to the line indicated by the given id.
 * PARAMETERS:	objMap			The Map to add the line/point to.
 *							strLineId		The ID of the line to add the point to.
 *							objLatLng   The lat/lng point to add to the line.
 *							iIndex			Optional. The place in the line at which to add the point.  Defaults to the end.
 *							strRouteType Optional. The type of 'directions' route to use: walking or driving. If not given or blank, use direct route.
 *							strColor		Optional. The RGB color to use when adding the first point to a new line. Defaults to the color in the settings.
 *							iWidth			Optional. The width to use when adding the first point to a new line. Defaults to the width in the settings.
 *							dOpacity		Optional. The opacity to use when adding the first point to a new line. Defaults to the opacity in the settings.
 * RETURNS:	The newly created goroam.google.maps.html_overlay
 */
goroam.google.maps.add_point_to_line = function(objMap, strLineId, objLatLng, iIndex, strRouteType, strColor, iWidth, dOpacity) {
	var objLine;
	// Load line settings
	strRouteType = ((strRouteType || '') == '' ? goroam.settings.line_on_roads : strRouteType);
	var bolFollowRoads = false;
	switch (strRouteType) {
		case 'walking':
		case 'driving':
		case 'yes':
			bolFollowRoads = true;
	}
	strColor = (strColor || goroam.settings.line_color);
	dOpacity = (dOpacity || parseFloat(goroam.settings.line_opacity));
	iWidth = (iWidth || parseInt(goroam.settings.line_width));
	// If the point passed in is a marker instead of a geo point
	if (objLatLng.getLatLng) objLatLng = objLatLng.getLatLng();
	// Different processing is required for lines that follow roads vs those that don't.
	if (bolFollowRoads) {
		var bolNewDirections = false;
		// For 'directions' lines, the stored line is an array of line segments.
		objLine = objMap.get_line_array()[strLineId];
		var iSegmentIndex = 0;
		if (objLine) {
			// The segment index is linked to the given index, or is the index of the latest segment if the index is not given.
			iSegmentIndex = (iIndex ? Math.floor(iIndex / parseInt('25')) : objLine.length - 1);
			// If the segment has hit the maximum, increase the segment index by 1.
			if (objLine[iSegmentIndex] && objLine[iSegmentIndex]._waypoints && objLine[iSegmentIndex]._waypoints.length == parseInt('25')) iSegmentIndex++;
		} else {
			objLine = [];
			objMap.get_line_array()[strLineId] = objLine;
		}
		if (!objLine[iSegmentIndex]) {
			// Walking directions requires a DIV for the directions.
			objLine[iSegmentIndex] = new google.maps.Directions(undefined, (strRouteType == 'walking' ? goroam.google.maps.get_hidden_directions_container(iSegmentIndex) : undefined));
			bolNewDirections = true;
		}
		// Switch to dealing only with the individual segment
		objLine = objLine[iSegmentIndex];
		if (!objLine._waypoints) objLine._waypoints = [];
		// Calculate the index within the given segment.
		iIndex = (iIndex ? iIndex - (iSegmentIndex * parseInt('25')) + iSegmentIndex : objLine._waypoints.length);
		if (iIndex < 0) iIndex = 0;
		// Subsequent segments should include the final point of the previous segment to correctly connect the lines.
		if (iSegmentIndex > 0) {
			if (iIndex == 0) {
				var objWaypoints = objMap.get_line_array()[strLineId][iSegmentIndex - 1]._waypoints;
				objLine._waypoints[0] = objWaypoints[objWaypoints.length - 1];
				iIndex++;
			}
		}
		objLine._waypoints[iIndex] = objLatLng.toString();
		objLine.clear();
		var iTravelMode = G_TRAVEL_MODE_DRIVING;
		if (strRouteType == 'walking') iTravelMode = G_TRAVEL_MODE_WALKING;
		objLine._updated = true;
		// Google maps only allows a certain number of directions requests within a period of time, so delay the request until the line is more
		// fully loaded.
		window.setTimeout(function() {
			if (objLine._updated) {
				objLine.loadFromWaypoints(objLine._waypoints, { getPolyline: true, preserveViewport: true, travelMode: iTravelMode });
				objLine._updated = false;
			}
		}, parseInt('100'));
		if (bolNewDirections) objMap.add_directions_line(objLine, strColor, iWidth, dOpacity, iTravelMode);
	} else {
		objLine = objMap.get_line_array()[strLineId];
		if (!objLine) {
			objLine = new google.maps.Polyline([], strColor, iWidth, dOpacity);
			objMap.addOverlay(objLine);
			objMap.get_line_array()[strLineId] = objLine;
		}
		iIndex = (iIndex || objLine.getVertexCount());
		objLine.insertVertex(iIndex, objLatLng);
	}
	objMap.extend_bounds(objLatLng);
}



/********************************
 * FUNCTION:	create_focus_overlay
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.01.29
 * DESCRIPTION:	This function creates an html_overlay.
 * PARAMETERS:	objLatLng   The lat/lng point where the overlay should be placed.
 * RETURNS:	The newly created goroam.google.maps.html_overlay
 */
goroam.google.maps.create_focus_overlay = function(objLatLng) {
  var objResult = new goroam.google.maps.html_overlay({lat_lng: objLatLng, class_name: 'gr_focus_overlay'});
  objResult.__redraw = objResult.redraw;
  objResult.redraw = function(bolForce) {
    var _self = this;
    // Turn on stage 1.
    goroam.dhtml.edit_class_name(_self.get_container(), 'focus_overlay_stage1');
    _self.__redraw(bolForce);
    window.setTimeout(function() {
      goroam.dhtml.edit_class_name(_self.get_container(), 'focus_overlay_stage2', 'focus_overlay_stage1');
      _self.__redraw(bolForce);
      window.setTimeout(function() {
        goroam.dhtml.edit_class_name(_self.get_container(), 'focus_overlay_stage3', 'focus_overlay_stage2');
        _self.__redraw(bolForce);
        window.setTimeout(function() {
          goroam.dhtml.edit_class_name(_self.get_container(), '', 'focus_overlay_stage3');
          _self.__redraw(bolForce);
          _self.map.removeOverlay(_self);
        }, parseInt('500'));
      }, parseInt('500'));
    }, parseInt('500'));
  }
  return objResult;
}



/********************************
 * FUNCTION:	create_marker
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.01.29
 * DESCRIPTION:	This function creates a Google Maps Marker, with some default settings.
 * PARAMETERS:	The same as a typical google.maps.Marker:
 *              objLatLng   The lat/lng point where the marker should be placed.
 *              objOptions  The options to use when creating the marker.
 * RETURNS:	The newly created google.maps.Marker
 */
goroam.google.maps.create_marker = function(objLatLng, objOptions) {
  var bolDraggable = false;
  if (objOptions && objOptions.draggable) bolDraggable = objOptions.draggable;
  if (!objOptions) objOptions = {};
  objOptions.draggable = true;
  objOptions.icon = goroam.google.maps.create_stock_icon(goroam.settings.marker_icon);
  var objResult = new google.maps.Marker(objLatLng, objOptions);
  if (!bolDraggable) objResult.disableDragging();
  return objResult;
}



/********************************
 * FUNCTION:	create_icon
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.02.27
 * DESCRIPTION:	This function creates a Google Maps Icon, with settings based on the given settings.
 * PARAMETERS:	objSettings     The settings to use to create the icon.
 *              strImageFolder  A root folder to use for image settings; can be blank.
 * RETURNS:     The newly created google.maps.Icon
 */
goroam.google.maps.create_icon = function(objSettings, strImageFolder) {
  var objResult = new google.maps.Icon();
  strImageFolder = (strImageFolder || '');
  for (var strName in objSettings) {
    switch (strName) {
      case 'image':
      case 'printImage':
      case 'printShadow':
      case 'shadow':
      case 'transparent':
        objResult[strName] = strImageFolder + objSettings[strName];
        break;
      default:
        objResult[strName] = objSettings[strName];
    }
  }
  return objResult;
}



/********************************
 * FUNCTION:	create_stock_icon
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.02.27
 * DESCRIPTION:	This function creates a Google Maps Icon, with settings based on the requested icon type.
 * PARAMETERS:	strType   The type of icon being requested
 * RETURNS:     The newly created google.maps.Icon
 */
goroam.google.maps.create_stock_icon = function(strType) {
  strType = (strType || goroam.enm.icons.pushpin.orange).toLowerCase();
  var strRootFolder = goroam.settings.root_url + '/images/icons/markers/';
  var objSettings, objResult;
  switch (strType) {
    case goroam.enm.icons.pushpin.blue:
    case goroam.enm.icons.pushpin.green:
    case goroam.enm.icons.pushpin.orange:
    case goroam.enm.icons.pushpin.red:
      objSettings = { iconAnchor: new google.maps.Point(9,32), iconSize: new google.maps.Size(32,32), image: 'red_pushpin.png', imageMap: [9,32, 10,29, 11,25, 5,16, 10, 12, 14,6, 12,2, 14,0, 18,0, 26,6, 21,15, 23,18, 23,22, 20,24, 14,25, 11,32], infoWindowAnchor: new google.maps.Point(9,32), printImage: 'red_pushpin.gif', printShadow: 'pushpin_shadow.gif', shadow: 'pushpin_shadow.png', shadowSize: new google.maps.Size(59,32), transparent: 'pushpin_transparent.png' };
      switch (strType) {
        case goroam.enm.icons.pushpin.blue:
          objSettings.image = 'blue_pushpin.png';
          objSettings.printImage = 'blue_pushpin.gif';
          break;
        case goroam.enm.icons.pushpin.green:
          objSettings.image = 'green_pushpin.png';
          objSettings.printImage = 'green_pushpin.gif';
          break;
        case goroam.enm.icons.pushpin.orange:
          objSettings.image = 'orange_pushpin.png';
          objSettings.printImage = 'orange_pushpin.gif';
          break;
      }
      break;
    case goroam.enm.icons.goroam:
      objSettings = { iconAnchor: new google.maps.Point(8,40), iconSize: new google.maps.Size(34,40), image: 'goroam.png', imageMap: [8,40, 5,31, 0,22, 0,15, 4,8, 8,4, 26,0, 34,0, 34,7, 31,9, 34,13, 34,24, 26,32, 16,35, 11,40 ], infoWindowAnchor: new google.maps.Point(8,40), printImage: 'goroam.gif', printShadow: 'goroam_shadow.gif', shadow: 'goroam_shadow.png', shadowSize: new google.maps.Size(55,40), transparent: 'goroam_transparent.png' };
      break;
  }
  if (objSettings) objResult = goroam.google.maps.create_icon(objSettings, strRootFolder);
  return objResult;
}



/********************************
 * FUNCTION:	find_location
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2006.08.24
 * DESCRIPTION:	This function takes a string describing a location, searches for it and if
 *		found it calls the given callback function.
 * PARAMETERS:	objMap			The GMap to recenter.
 *				strLocation		The location to find.
 *				fCallAfter		Optional. A function to call when done processing the search.  It will be passed the lat/lng
 *                      of the location, a zoom level (if found), and the found address.
 *				fCallAfterNotFound	Optional. A function to call when done processing the search, without finding the lat/lng.
 * RETURNS:	none
 */
goroam.google.maps.find_location = function(objMap, strLocation, fCallAfter, fCallAfterNotFound) {
	var fCallback = function(objGeoResult) {
		var objPoint, strAddress, objPlacemark;
		if ((objGeoResult) && (objGeoResult.Status)) {
			if (objGeoResult.Status.code == 500) {
				// Retry on HTTP failure. Sometimes this results in double attempts
				// but I think the extra is better than not showing any result.
        goroam.google.maps.find_location(objMap, strLocation, fCallAfter);
			} else {
				if ((objGeoResult) && (objGeoResult.Placemark)) {
					if ((objGeoResult.Placemark.length) && (objGeoResult.Placemark.length > 0)) {
            objPlacemark = objGeoResult.Placemark[0];
						objPoint = objPlacemark.Point;
            strAddress = objPlacemark.address;
					} else if (objGeoResult.Placemark.Point) {
            objPlacemark = objGeoResult.Placemark;
						objPoint = objPlacemark.Point;
            strAddress = objPlacemark.address;
					}
					if ((objPoint) && (objPoint.coordinates) && (objPoint.coordinates.length >= 2)) {
						objPoint = new google.maps.LatLng(objPoint.coordinates[1], objPoint.coordinates[0]);
					} else {
						objPoint = undefined;
            strAddress = undefined;
            objPlacemark = undefined;
					}
				}
				if (objPoint) {
          if (fCallAfter) fCallAfter.call(objMap, objPoint, strAddress, objPlacemark);
				} else {
					if (fCallAfterNotFound) {
						fCallAfterNotFound();
					} else {
						alert('Sorry, the location was not found: ' + strLocation);
					}
				}
				//objMap.unset_processing();
			}
		}
	};
	// Search for the location using the GMaps geocoder.
	//objMap.set_processing();
	var objGeocoder = new google.maps.ClientGeocoder();
	objGeocoder.getLocations(strLocation, fCallback);
}



/********************************
 * FUNCTION:	get_address
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.01.29
 * DESCRIPTION:	This function translates the given lat/lng point to an address.
 * PARAMETERS:	objLatLng   The point to look up.
 *              fCallAfter  The function to call if the address is found.  Will be passed the address string and the
 *                          more detailed GoogleMaps Placemark object.
 * RETURNS:		  none
 */
goroam.google.maps.get_address = function(objLatLng, fCallAfter) {
  var fCallback = function(objGeoResult) {
    var objPlacemark;
    if (objGeoResult && objGeoResult.Placemark && (objGeoResult.Placemark.length > 0)) {
      // Find the most accurate placemark.
      var arrAccuracy = [];
      for (var i = 0; i < objGeoResult.Placemark.length; i++) {
        arrAccuracy[objGeoResult.Placemark[i].AddressDetails.Accuracy] = objGeoResult.Placemark[i];
      }
      if (arrAccuracy.length > 0) objPlacemark = arrAccuracy[arrAccuracy.length - 1];
    } else if (objGeoResult && objGeoResult.Placemark) {
      objPlacemark = objGeoResult.Placemark;
    }
    if (objPlacemark) {
      if (fCallAfter) fCallAfter.call(null, objPlacemark.address, objPlacemark);
    } else {
      alert('Address not found.');
    }
  }
	var objGeocoder = new google.maps.ClientGeocoder();
	objGeocoder.getLocations(objLatLng, fCallback);
}



/********************************
 * FUNCTION:	get_hidden_directions_container
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.10.22
 * DESCRIPTION:	This function gets/creates the container on the page that holds the hidden iframes used in the
 *              cross-domain communication.
 * PARAMETERS:	None
 * RETURNS:	None
 */
goroam.google.maps.get_hidden_directions_container = function(iIndex) {
	iIndex = (iIndex || 0);
	if (!goroam.google.maps.__hidden_directions_container) goroam.google.maps.__hidden_directions_container = [];
  if (!goroam.google.maps.__hidden_directions_container[iIndex]) {
    goroam.google.maps.__hidden_directions_container[iIndex] = document.createElement('div');
    //goroam.google.maps.__hidden_directions_container[iIndex].style.display = 'none';
    document.getElementsByTagName('body')[0].appendChild(goroam.google.maps.__hidden_directions_container[iIndex]);
  }
  return goroam.google.maps.__hidden_directions_container[iIndex];
}



/********************************
 * FUNCTION:	get_map
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.01.27
 * DESCRIPTION:	This function gets the active map on the page.
 * PARAMETERS:	none
 * RETURNS:		  GoogleMaps Map2 object
 */
goroam.google.maps.get_map = function(strMapId) {
  if (strMapId && (strMapId != '')) goroam.google.maps.load_map_from_settings(strMapId);
  return (goroam.google.maps.__page_maps[strMapId] || goroam.google.maps.__active_map);
}



/********************************
 * FUNCTION:	load
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.01.25
 * DESCRIPTION:	This function is a generalized load function to load a map page.  It will call the optional
 *              function if one is passed in.
 * PARAMETERS:	fFunc   Optional. A more specific function to call during the load process.
 * RETURNS:		  none
 */
goroam.google.maps.load = function(fFunc) {
  if (google.maps.BrowserIsCompatible()) {
    // Extend the Google Maps objects
    goroam.google.maps.extend_functionality();
    // Try to prevent memory leaks.
    goroam.event.addDomListener(window, 'unload', goroam.google.maps.unload);
    // Call the function if given.
    if (fFunc) fFunc();
  } else {
    alert('GoRoam Error: Sorry, your browser is not supported by the maps on this page');
  }
}



/********************************
 * FUNCTION:	load_active_map
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.01.25
 * DESCRIPTION:	This function simply loads the active map and keeps track of the resultant map object.
 * PARAMETERS:	none
 * RETURNS:		  none
 */
goroam.google.maps.load_active_map = function() {
  // Load the active map.
  if (goroam.google.maps.load_map_from_settings(goroam.google.maps.__active_map_id)) goroam.google.maps.__active_map = goroam.google.maps.__page_maps[goroam.google.maps.__active_map_id];
}



/********************************
 * FUNCTION:	load_from_page
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.01.25
 * DESCRIPTION:	This function loads the map data from the HTML elements on the page.
 * PARAMETERS:	none
 * RETURNS:		  none
 */
goroam.google.maps.load_from_page = function() {
  goroam.google.maps.load_active_map();
  // Pull all HTML elements with lat/lng defined.
  var arrLatLngElements = goroam.dhtml.get_elements_with_attribute(goroam.google.maps.__lat_attr + ' , ' + goroam.google.maps.__addr_attr);
  for (var i = 0; i < arrLatLngElements.length; i++) {
    if (((arrLatLngElements[i].getAttribute(goroam.google.maps.__lat_attr) || '') == '') || ((arrLatLngElements[i].getAttribute(goroam.google.maps.__lng_attr) || '') == '')) {
			// If lat or lng is missing, lookup the address using the addr attribute.
		  goroam.google.maps.find_location(arrLatLngElements[i], arrLatLngElements[i].getAttribute(goroam.google.maps.__addr_attr), function(objPoint, strAddress, objPlacemark) {
				if (objPoint) {
					this.setAttribute(goroam.google.maps.__lat_attr, objPoint.lat());
					this.setAttribute(goroam.google.maps.__lng_attr, objPoint.lng());
					goroam.google.maps.process_geotagged_element(this);
				}
				goroam.google.maps.__count_of_processing_addr--;
				if (goroam.google.maps.__count_of_processing_addr <= 0) goroam.google.maps.process_all_geolists();
		  }, function() {
				goroam.google.maps.__count_of_processing_addr--;
				if (goroam.google.maps.__count_of_processing_addr <= 0) goroam.google.maps.process_all_geolists();
			});
			goroam.google.maps.__count_of_processing_addr++;
    } else {
			// If both lat and lng are given, use those values every time, whether not not addr is defined.
			goroam.google.maps.process_geotagged_element(arrLatLngElements[i]);
		}
  }
	if (goroam.google.maps.__count_of_processing_addr <= 0) goroam.google.maps.process_all_geolists();
}



/********************************
 * FUNCTION:	load_map_from_settings
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.01.25
 * DESCRIPTION:	This function loads a map into the container with the given id, using the settings
 *              defined by the javascript request.
 * PARAMETERS:	strId   The id of the container in which to load the map.
 * RETURNS:		  boolean True if the map was loaded successfully; false if not.
 */
goroam.google.maps.load_map_from_settings = function(strId) {
  var bolResult = false;
  var elContainer = document.getElementById(strId);
  if (elContainer && !goroam.google.maps.__page_maps[strId]) {
    var elAnchor = document.createElement('a');
    elAnchor.name = strId;
    elContainer.insertBefore(elAnchor, elContainer.firstChild);
    var objMap = new google.maps.Map2(elContainer);
    objMap.setCenter(new google.maps.LatLng(goroam.settings.lat, goroam.settings.lng), parseInt(goroam.settings.zoom));
    // Add map controls (zooming, map types, etc.)
    try {
			switch (goroam.settings.controls) {
				case 'all':
				  objMap.addControl(new GLargeMapControl());
				  objMap.addControl(new GMapTypeControl());
				  objMap.addControl(new GOverviewMapControl());
				  objMap.addControl(new GScaleControl());
					break;
      	case 'all_small':
				  objMap.addControl(new GSmallMapControl());
				  objMap.addControl(new GMenuMapTypeControl());
				  objMap.addControl(new GOverviewMapControl());
				  objMap.addControl(new GScaleControl());
					break;
      	case 'min':
				  objMap.addControl(new GMenuMapTypeControl());
				  objMap.addControl(new GSmallZoomControl());
					break;
      	case 'none':
					break;
			}
    } catch (e) {
      objMap.addControl(new GSmallMapControl());
      objMap.addControl(new GMapTypeControl());
    }
    goroam.google.maps.__page_maps[strId] = objMap;
    bolResult = true;
  }
  return bolResult;
}



/********************************
 * FUNCTION:	add_map_prompt, over_map_element, leave_map_element
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.02.17
 * DESCRIPTION:	This function displays a prompt to show the associated HTML element on a map.  To be used with the
 *              onmouseover/onmouseout events of the element.
 * PARAMETERS:	none
 * RETURNS:		  none
 */
goroam.google.maps.add_map_prompt = function(elElement) {
  if (!elElement.__map_prompt) {
		var strPosition = goroam.settings.position;
		var strOutside = '';
		if (strPosition.left(4) == 'out_') {
			strOutside = '_' + strPosition;
			strPosition = strPosition.right(2);
		}
    // Add event triggers to the element to show/hide the map prompt.
    goroam.event.bindDom(elElement, 'mouseover', elElement, goroam.dhtml.set_mouseover);
    goroam.event.bindDom(elElement, 'mouseover', elElement, goroam.google.maps.over_map_element);
    goroam.event.bindDom(elElement, 'mouseout', elElement, goroam.dhtml.unset_mouseover);
    goroam.event.bindDom(elElement, 'mouseout', elElement, goroam.google.maps.leave_map_element);
    goroam.event.bindDom(elElement, 'click', elElement, function(e) { if (this.__map_prompt && this.__map_prompt.__mouseover) goroam.dhtml.cancel_bubble(e); });
		// Create an anchor to be able to reference this location later.
    var elAnchor = document.createElement('a');
		elAnchor.style.position = 'absolute';
		elAnchor.name = '_latlng' + goroam.google.maps.__count_of_page_lat_lng++;
		elAnchor.id = elAnchor.name;
		if (goroam.browser.host_info().name == goroam.enm.browsers.ie) elAnchor.innerHTML = '&nbsp;';
    // Create a link to trigger the onclick event and move to the map's anchor on the page.
    var elLink = document.createElement('a');
    elLink.href = '#' + goroam.system.set_default(elElement.getAttribute(goroam.google.maps.__map_attr), goroam.settings.map);
    // The link is just a transparent image that fits the size of the container; this allows for controlling
    // the display through CSS.
    elLink.innerHTML = '<img src="' + goroam.settings.root_url + '/images/px.gif" style="border-width:0px; height:100%; width:100%;" title="Show on map" />';
    // Create the container that holds the link and is the vessel for display styles.
    elElement.__map_prompt = document.createElement('div');
		elElement.__map_prompt.__anchor = elAnchor.name;
    elElement.__map_prompt.className = 'gr_map_prompt gr_map_prompt_' + strPosition;
    elElement.__map_prompt.appendChild(elLink);
    // When hovering over the map prompt, use this class name instead of the default.
    elElement.__map_prompt.__mouseover_class_name = 'map_prompt_hover';
    elElement.__map_prompt.__mouseout_class_name = 'map_prompt_hover_element';
    goroam.event.bindDom(elLink, 'click', elElement, goroam.google.maps.show_on_map);
    goroam.event.bindDom(elElement.__map_prompt, 'mouseover', elElement.__map_prompt, goroam.dhtml.set_mouseover);
    goroam.event.bindDom(elElement.__map_prompt, 'mouseout', elElement.__map_prompt, goroam.dhtml.unset_mouseover);
    goroam.event.bindDom(elElement.__map_prompt, 'mouseout', elElement, goroam.google.maps.leave_map_element);
    // Make sure the HTML element is positioned, so that the absolute positioning of the map prompt works correctly.
    if (elElement.style.position != 'absolute') elElement.style.position = 'relative';
    if (elElement.tagName.toLowerCase() == 'img') {
      // Images do not have child elements, so drop it in a parent span and add the map prompt as a sibling node,
      // instead of a child node.
      var elSpan = document.createElement('span');
      // Transfer positioning to the parent span.
      elSpan.style.position = elElement.style.position;
      elElement.style.position = 'relative';
      elSpan.style.top = elElement.style.top;
      elSpan.style.bottom = elElement.style.bottom;
      elSpan.style.left = elElement.style.left;
      elSpan.style.right = elElement.style.right;
			elSpan.className = 'gr_img_container';
      // IE Hack to give the parent element "layout".
      if (goroam.browser.host_info().name == goroam.enm.browsers.ie) elSpan.style.zoom = 1;
      // Clear positioning from the image so that is uses the positioning transferred to the parent span.
      elElement.style.top = '';
      elElement.style.bottom = '';
      elElement.style.left = '';
      elElement.style.right = '';
      // Insert the parent span into the DOM tree.
      elElement.parentNode.replaceChild(elSpan, elElement);
      elSpan.appendChild(elElement);
      elSpan.appendChild(elElement.__map_prompt);
			// Insert an anchor to be able to navigate to this location.
			elSpan.insertBefore(elAnchor, elElement);
    } else {
      // IE Hack to give the parent element "layout".
      if ((goroam.browser.host_info().name == goroam.enm.browsers.ie) && elElement.currentStyle && !elElement.currentStyle.hasLayout) elElement.style.zoom = 1;
      elElement.appendChild(elElement.__map_prompt);
			// Tag the element with a class name so it can be accessed through CSS.
			goroam.dhtml.edit_class_name(elElement, 'tagged_element' + strOutside);
			// Insert an anchor to be able to navigate to this location.
			elElement.parentNode.insertBefore(elAnchor, elElement);
    }
  }
	return elElement.__map_prompt.__anchor;
}
goroam.google.maps.over_map_element = function() {
  if (!this.__map_prompt.__mouseover) goroam.dhtml.edit_class_name(this.__map_prompt, 'map_prompt_hover_element');
}
goroam.google.maps.leave_map_element = function() {
  var self = this;
  window.setTimeout(function() { if (!self.__mouseover && !self.__map_prompt.__mouseover) goroam.dhtml.edit_class_name(self.__map_prompt, '', 'map_prompt_hover_element'); }, parseInt('200'));
}



/********************************
 * FUNCTION:	process_all_geolists
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.10.14
 * DESCRIPTION:	This function processes all a geotagged HTML lists (UL or OL).
 * PARAMETERS:	none
 * RETURNS:		  none
 */
goroam.google.maps.process_all_geolists = function() {
	// Pull and process all lists with geotagged children.
	var arrTagNames = ['ul', 'ol'];
	for (var j = 0; j < arrTagNames.length; j++) {
		var arrLists = document.getElementsByTagName(arrTagNames[j]);
		for (var i = 0; i < arrLists.length; i++) {
			goroam.google.maps.process_geolist(arrLists[i]);
		}
	}
}



/********************************
 * FUNCTION:	process_geolist
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.10.14
 * DESCRIPTION:	This function loads a geotagged HTML list (UL or OL).
 * PARAMETERS:	elList		The HTML UL or OL element to process.
 * RETURNS:		  none
 */
goroam.google.maps.process_geolist = function(elList) {
	var arrCounts = {};
	if ((elList.tagName.toLowerCase() == 'ul') || (elList.tagName.toLowerCase() == 'ol')) {
		// If the list has no text, assume it is only there to define a map route.
		if (goroam.dhtml.get_inner_text(elList).compress_whitespace().trim() == '') goroam.dhtml.edit_class_name(elList, 'map_route');
		var strRouteType = (elList.className.match(/\b(gr_)?map_route_(walking|driving)\b/i) || [''])[0].right_of('map_route_').toLowerCase();
		// Pull all geotagged children.
		var arrTagged = goroam.dhtml.get_elements_with_attribute(goroam.google.maps.__lat_attr + ' , ' + goroam.google.maps.__addr_attr, elList);
		if (arrTagged.length > 0) {
			goroam.dhtml.edit_class_name(elList, 'geo_list');
			var strId = (elList.id || '');
			var strColor = elList.style.color;
			var iWidth = parseInt(elList.style.width);
			var dOpacity = elList.style.opacity;
			if (strId == '') elList.id = goroam.math.random_string(30);
			for (var i = 0; i < arrTagged.length; i++) {
				var objMap = goroam.google.maps.get_map(arrTagged[i].getAttribute(goroam.google.maps.__map_attr));
				if (objMap) {
					var strMapId = objMap.getContainer().id;
					if (arrCounts[strMapId]) {
						arrCounts[strMapId]++;
					} else {
						arrCounts[strMapId] = 0;
					}
					if (((arrTagged[i].getAttribute(goroam.google.maps.__lat_attr) || '') == '') || ((arrTagged[i].getAttribute(goroam.google.maps.__lng_attr) || '') == '')) {
						// If lat or lng is missing, lookup the address using the addr attribute.
						var iIndex = arrCounts[strMapId];
						goroam.google.maps.find_location(arrTagged[i], arrTagged[i].getAttribute(goroam.google.maps.__addr_attr), function(objPoint, strAddress, objPlacemark) {
							if (objPoint) {
								this.setAttribute(goroam.google.maps.__lat_attr, objPoint.lat());
								this.setAttribute(goroam.google.maps.__lng_attr, objPoint.lng());
								objMap.add_point_to_line(strId, objPoint, iIndex, strRouteType, strColor, iWidth, dOpacity);
							}
						}, true);
					} else {
						// If both lat and lng are given, use those values every time, whether not not addr is defined.
						objMap.add_point_to_line(strId, new google.maps.LatLng(arrTagged[i].getAttribute(goroam.google.maps.__lat_attr), arrTagged[i].getAttribute(goroam.google.maps.__lng_attr)), arrCounts[strMapId], strRouteType, strColor, iWidth, dOpacity);
					}
					if (arrTagged[i].tagName.toLowerCase() == 'li') goroam.dhtml.edit_class_name(arrTagged[i], 'geo_tag_only');
				}
			}
		}
	}
}



/********************************
 * FUNCTION:	process_geotagged_element
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.10.12
 * DESCRIPTION:	This function encapsulated the logic required to process an element that has been tagged with lat/lng attributes.
 * PARAMETERS:	elElement		The HTML element to process.
 * RETURNS:		  none
 */
goroam.google.maps.process_geotagged_element = function(elElement) {
	var bolShowOnMap = true;
	// For geopoints that indicate a line, don't add a marker on the map.
	if (elElement.tagName.toLowerCase() == 'li') {
		var objData = goroam.dhtml.get_inner_text(elElement);
		if ((objData.title == '' || objData.title == null) && (objData.value == '' || objData.value == null)) bolShowOnMap = false;
	}
	if (bolShowOnMap) {
		// Add a map prompt to the element.
		var strAnchor = '';
		if ((elElement.tagName.toLowerCase() == 'img') || (goroam.dhtml.get_inner_text(elElement) != '')) strAnchor = goroam.google.maps.add_map_prompt(elElement);
		var objMap = goroam.google.maps.get_map(elElement.getAttribute(goroam.google.maps.__map_attr));
		if (objMap) {
			var objData = goroam.dhtml.get_element_data(elElement);
			var strAnchorText = (objData.title || '');
			if (strAnchorText == '') strAnchorText = (objData.value || '');
			if (strAnchorText.length > 25) strAnchorText = strAnchorText.left(25) + '...';
		  var objLatLng = objMap.add_lat_lng(elElement.getAttribute(goroam.google.maps.__lat_attr), elElement.getAttribute(goroam.google.maps.__lng_attr), strAnchor, strAnchorText);
		  // Add event triggers to highlight the element when hovering over the map marker.
		  goroam.event.bind(objLatLng, 'mouseover', elElement, goroam.dhtml.highlight);
		  goroam.event.bind(objLatLng, 'mouseout', elElement, goroam.dhtml.unhighlight);
			if (objLatLng.__menu) {
				goroam.event.bindDom(objLatLng.__menu.get_container().lastChild, 'mouseover', elElement, goroam.dhtml.highlight);
				goroam.event.bindDom(objLatLng.__menu.get_container().lastChild, 'mouseout', elElement, goroam.dhtml.unhighlight);
			}
		  // Resize and recenter the maps based on the loaded lat/lng points.
		  objMap.resize_to_bounds();
		}
	}
}



/********************************
 * FUNCTION:	show_on_map
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.02.17
 * DESCRIPTION:	This function displays an item on a map, which may be either on the page on in a popup (based on
 *              the api settings).
 * PARAMETERS:	none
 * RETURNS:		  none
 */
goroam.google.maps.show_on_map = function(e) {
  var bolResult = false;
  var bolPopup = (goroam.settings.popup === true);
  var bolPopupWindow = (goroam.settings.popup_window === true);
  var strMapId = (this.getAttribute(goroam.google.maps.__map_attr) || goroam.google.maps.__active_map_id);
  var objMap = goroam.google.maps.get_map(strMapId);
  if (objMap && !(bolPopup || bolPopupWindow)) {
    var objLatLng = new google.maps.LatLng(this.getAttribute(goroam.google.maps.__lat_attr), this.getAttribute(goroam.google.maps.__lng_attr));
    objMap.setCenter(objLatLng);
    objMap.show_focus(objLatLng);
    bolResult = true;
  } else {
    goroam.controls.popup_map(new google.maps.LatLng(this.getAttribute(goroam.google.maps.__lat_attr), this.getAttribute(goroam.google.maps.__lng_attr)));
    goroam.dhtml.cancel_bubble(e);
  }
  return bolResult;
}



/********************************
 * FUNCTION:	unload
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.01.25
 * DESCRIPTION:	This function does any necessary cleanup when the user leaves the page.
 * PARAMETERS:	none
 * RETURNS:		  none
 */
goroam.google.maps.unload = function() {
  google.maps.Unload();
}


/********************************
 * FUNCTION:	event functions
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.02.01
 * DESCRIPTION:	These functions help in event management.  The goroam.event namespace provides a layer of abstraction in
 *              case the event management should move off of the Google GEvent object.
 */
goroam.event.addListener = function() {
  google.maps.Event.addListener.apply(arguments[0], arguments);
}
goroam.event.addDomListener = function() {
  google.maps.Event.addDomListener.apply(arguments[0], arguments);
}
goroam.event.bind = function() {
  google.maps.Event.bind.apply(arguments[0], arguments);
}
goroam.event.bindDom = function() {
  google.maps.Event.bindDom.apply(arguments[0], arguments);
}
goroam.event.trigger = function() {
  google.maps.Event.trigger.apply(arguments[0], arguments);
}



/********************************
 * FUNCTION:	arguments_to_array
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.02.02
 * DESCRIPTION:	The [Function].arguments array is an array but seems to be read-only (can't
 *		call push or pop or anything on it).  This I needed this function to convert
 *		that array into a real array that I can manipulate.
 * PARAMETERS:	args    Array.  Pass 'arguments' into this function to translate it into a normal array.
 * RETURNS:	array		The [Function].arguments array as a real array.
 */
goroam.system.arguments_to_array = function(args) {
  var arrResult = [];
  for (var i = 0; i < args.length; i++) arrResult[i] = args[i];
  return arrResult;
}



/********************************
 * FUNCTION:	is_numeric
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2006.11.11
 * DESCRIPTION:	This function tests to see if a variable is a number.
 * PARAMETERS:	obj		The variable to test.
 * RETURNS:	Boolean		True if the variable is a number; False if not.
 */
goroam.system.is_numeric = function(obj) {
	return (!isNaN(obj));
}



/********************************
 * FUNCTION:	set_default
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2006.09.19
 * DESCRIPTION:	This function returns either the value, or the default if the value is undefined/null.
 * PARAMETERS:	objVal		The value.
 *		objDefault	The default, if value is undefined/null
 * RETURNS:	variant		The value or the default.
 */
goroam.system.set_default = function(objVal, objDefault) {
  return ((objVal == undefined) || (objVal == null)) ? objDefault : objVal;
}



/********************************
 * FUNCTION:	is_array
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2006.09.20
 * DESCRIPTION:	This function tests to see if a variable is an array.
 * BROWSERS:	IE		Good
 *		Firefox		Good
 *		Opera		Good
 *		Netscape	Good
 * PARAMETERS:	obj		The variable to test.
 * RETURNS:	Boolean		True if the variable is an array; False if not.
 */
goroam.system.is_array = function(obj) {
	var bolResult = false;
	try {
		if (obj.constructor == Array) bolResult = true;
	} catch (e) {}
	return bolResult;
}



/********************************
 * FUNCTION:	is_object
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2006.09.20
 * DESCRIPTION:	This function tests to see if a variable is an object.
 * PARAMETERS:	obj		The variable to test.
 * RETURNS:	Boolean		True if the variable is an object; False if not.
 */
goroam.system.is_object = function(obj) {
	if ((typeof obj == 'object') && (!goroam.system.is_array(obj)))
		return true;
	else
		return false;
}



/********************************
 * FUNCTION:	object_to_string
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.01.29
 * DESCRIPTION:	This function translates an object into a string.
 * PARAMETERS:	obj		        The object to examine.
 *              strParentName The name of the parent object.
 * RETURNS:	    string        The object, as a string.
 */
goroam.system.object_to_string = function(obj, str) {
  var strResult = '';
  str = (str || '');
  if (goroam.system.is_object(obj)) {
    for (var strName in obj) {
      strResult += goroam.system.object_to_string(obj[strName], str + ((str == '') ? '' : '.') + strName);
    }
  } else if (goroam.system.is_array(obj)) {
    for (var i = 0; i < obj.length; i++) {
      strResult += goroam.system.object_to_string(obj[i], str + ((str == '') ? '' : '[' + i + ']'));
    }
  } else {
    strResult = str + '=' + obj + '\n';
  }
  return strResult;
}


/********************************
 * FUNCTION:	append_to_body
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.03.03
 * DESCRIPTION:	This function appends the given HTML element to the body element of the page.
 * PARAMETERS:	elElement The HTML element to append to the BODY element.
 * RETURNS:	    none
 */
goroam.dhtml.append_to_body = function(elElement) {
  if (elElement) {
    var elBody = goroam.dhtml.get_body();
    if (elBody) elBody.appendChild(elElement);
  }
}



/********************************
 * FUNCTION:	cancel_bubble
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2006.02.20
 * DESCRIPTION:	This function cancels the event bubbling for the given event.
 * PARAMETERS:	evEvent	The event object to cancel the bubbing.
 * RETURNS:	None
 */
goroam.dhtml.cancel_bubble = function(evEvent) {
	evEvent = goroam.dhtml.get_event(evEvent);
  // For Internet Explorer
  if (evEvent && evEvent.cancelBubble) evEvent.cancelBubble = true;
  // For Mozilla
  if (evEvent && evEvent.stopPropagation) evEvent.stopPropagation();
  if (evEvent && evEvent.preventDefault) evEvent.preventDefault();
}



/********************************
 * FUNCTION:	edit_class_name
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.02.18
 * DESCRIPTION:	This function changes the class name on a given HTML element to include strAdd and removes strRemove.
 * PARAMETERS:	elElement   Optional. The HTML element to highlight.  Defaults to the calling object.
 *              strAdd      Optional. The class string to add to the existing class string.
 *              strRemove   Optional. The class string to remove from the existing class string.
 *              bolNoPrefix  Optional. False to use the GoRoam CSS prefix; True to use only the given string.
 * RETURNS:	none
 */
goroam.dhtml.edit_class_name = function(elElement, strAdd, strRemove, bolNoPrefix) {
  elElement = (elElement || this);
  strAdd = (strAdd || '');
  strRemove = (strRemove || '');
  if (!bolNoPrefix) {
    if (strAdd != '') strAdd = goroam.string.add_prefix_to_all_words(strAdd, 'gr_');
    if (strRemove != '') strRemove = goroam.string.add_prefix_to_all_words(strRemove, 'gr_');
  }
	if (!elElement.className) elElement.className = '';
  elElement.className = elElement.className.add_value(strAdd, strRemove, false, false);
}



/********************************
 * FUNCTION:	hide, hide_me, show_me, toggle, toggle_me
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.10.30
 * DESCRIPTION:	This function shows or hides the element.
 * PARAMETERS:	None    Show/hide is done on the calling object.
 * RETURNS:	none
 */
goroam.dhtml.show = function(elElement) {
	var strDisplay = (elElement.__display || '').toLowerCase();
	if ((strDisplay == 'none') || (strDisplay == '')) strDisplay = 'inline';
  elElement.style.display = strDisplay;
}
goroam.dhtml.show_me = function() {
	goroam.dhtml.show(this);
}
goroam.dhtml.hide = function(elElement) {
  elElement.__display = elElement.style.display;
	elElement.style.display = 'none';
}
goroam.dhtml.hide_me = function() {
	goroam.dhtml.hide(this);
}
goroam.dhtml.toggle = function(elElement) {
	if (goroam.dhtml.get_current_style(elElement, 'display') == 'none') {
		goroam.dhtml.show(elElement);
	} else {
		goroam.dhtml.hide(elElement);
	}
}
goroam.dhtml.toggle_me = function() {
	goroam.dhtml.toggle(this);
}



/********************************
 * FUNCTION:	highlight, unhighlight
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.01.25
 * DESCRIPTION:	This function adds a highlight/unhighlight class to the given HTML element.  The actual highlighting
 *              effects should be set in the CSS.
 * PARAMETERS:	None    Highlighting is done on the calling object.
 * RETURNS:	none
 */
goroam.dhtml.highlight = function() {
  goroam.dhtml.edit_class_name(this, 'highlight');
}
goroam.dhtml.unhighlight = function() {
  goroam.dhtml.edit_class_name(this, '', 'highlight');
}



/********************************
 * FUNCTION:	get_body
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2007.10.20
 * DESCRIPTION:	This function gets the body element of the page.
 * PARAMETERS:	none
 * RETURNS:	HTML Element	The BODY element
 */
goroam.dhtml.get_body = function() {
	var elResult;
	elResult = document.body;
	if (!elResult) elResult = document.getElementsByTagName("body")[0];
	return elResult;
}



/********************************
 * FUNCTION:	get_current_style
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2007.11.03
 * DESCRIPTION:	This function gets the current style of the element. This is different than simply using the 'style' property because 'style'
 *							does not take the CSS stylesheet into account; it is only what is set in the style property on the element.
 * PARAMETERS:	elElement		The element to examine.
 *							strStyle		The style property to look for.
 * RETURNS:			string			The assigned value of the style attribute for the element.
 */
goroam.dhtml.get_current_style = function(elElement, strStyle) {
	var strResult = '';
	if (elElement.currentStyle) {
		strResult = elElement.currentStyle[strStyle];
	} else if (document.defaultView && document.defaultView.getComputedStyle) {
		strResult = document.defaultView.getComputedStyle(elElement, null).getPropertyValue(strStyle);
	} else {
		strResult = elElement.style[strStyle];
	}
	return strResult.toLowerCase();
}



/********************************
 * FUNCTION:	get_element_data
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.02.16
 * DESCRIPTION:	This function examines the given HTML element and produces the data to send to the GeoGoRoam server.
 * PARAMETERS:	elElement   The HTML element to examine.
 * RETURNS:	    object      A title, text content (value), and data type parsed from the element.  See GoRoam's data_types
 *                          table for the mapping of data_type ids to what they mean.
 */
goroam.dhtml.get_element_data = function(elElement) {
  var objResult = {data_type: 0, value: null, title: null};
  if (elElement) {
    switch (elElement.tagName.toLowerCase()) {
      case 'a':
        objResult.data_type = 3;
        objResult.value = elElement.href;
        objResult.title = goroam.dhtml.get_inner_text(elElement);
        break;
      case 'img':
        objResult.data_type = 4;
        objResult.value = elElement.src;
        objResult.title = elElement.getAttribute('alt');
        if (!objResult.title) objResult.title = elElement.getAttribute('title');
        break;
      case 'input':
        switch (elElement.getAttribute('type').toLowerCase()) {
          case 'image':
            objResult.data_type = 4;
            objResult.value = elElement.src;
            objResult.title = elElement.getAttribute('title');
            break;
          default:
            objResult.data_type = 2;
            objResult.value = elElement.value;
            objResult.title = elElement.getAttribute('title');
        }
        break;
      case 'b':
      case 'body':
      case 'div':
      case 'em':
      case 'h1':
      case 'h2':
      case 'h3':
      case 'h4':
      case 'h5':
      case 'h6':
      case 'h7':
      case 'i':
      case 'li':
      case 'p':
      case 'span':
      case 'strong':
      case 'td':
      case 'u':
        objResult.data_type = 2;
        objResult.value = goroam.dhtml.get_inner_html(elElement);
        objResult.title = elElement.getAttribute('title');
        break;
      case 'textarea':
        objResult.data_type = 2;
        objResult.value = elElement.value;
        objResult.title = elElement.getAttribute('title');
        break;
      default:
        objResult.data_type = 1;
        objResult.value = goroam.dhtml.outer_html(elElement);
        objResult.title = elElement.getAttribute('title');
    }
  }
  return objResult;
}



/********************************
 * FUNCTION:	get_elements_in_class
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2008.05.13
 * DESCRIPTION:	This function gets all elements that have the given class set.
 * PARAMETERS:	strClassName	The class to look for.
 *				      elNode			  Optional.  The HTML element to look in.  Defaults to BODY.
 *              bolNoPrefix   Optional. True to ignore the GoRoam class prefix; False to use it.  Defaults to false.
 * RETURNS:		  array			    The list of found HTML elements.
 */
goroam.dhtml.get_elements_in_class = function(strClassName, elNode, bolNoPrefix) {
	var arrResult = [];
  strClassName = (strClassName || '');
  if (strClassName != '') {
    var strClassAttr = (goroam.browser.host_info().name == goroam.enm.browsers.ie ? 'className' : 'class');
	  var arrElements = goroam.dhtml.get_elements_with_attribute(strClassAttr, elNode);
    var arrClassName = [];
    if (arrElements.length > 0) {
      if (!bolNoPrefix) strClassName = goroam.string.add_prefix_to_all_words(strClassName, 'gr_');
      arrClassName = strClassName.compress_whitespace().split(' ');
      for (var i = 0; i < arrClassName.length; i++) arrClassName[i] = RegExp('\\b' + arrClassName[i] + '\\b', 'i');
    }
	  for (var i = 0; i < arrElements.length; i++) {
		  var strValue = (arrElements[i].getAttribute(strClassAttr) || '');
      for (var j = 0; j < arrClassName.length; j++) {
		    if (strValue.search(arrClassName[j]) >= 0) {
          arrResult.push(arrElements[i]);
          break;
        }
      }
	  }
  }
	return arrResult;
}



/********************************
 * FUNCTION:	get_elements_with_attribute
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2008.05.13
 * DESCRIPTION:	This function gets all elements that have the given attribute set
 *				to something other than an empty string.
 * BROWSERS:	IE		Good
 *		Firefox		Good
 *		Opera		Good
 *		Netscape	Good
 * PARAMETERS:	strAttribute	The attribute to look for.  Case-sensitive.  Can be comma-delimited list.
 *				elNode			Optional.  The HTML element to look in.  Defaults to BODY.
 * RETURNS:		array			The list of found HTML elements.
 */
goroam.dhtml.get_elements_with_attribute = function(strAttribute, elNode) {
	if (!elNode) elNode = document.getElementsByTagName("body")[0];
	var arrResult = [];
  strAttribute = strAttribute.toLowerCase();
	var arrAttributes = strAttribute.split_trim(',');
	var arrElements = elNode.getElementsByTagName("*");
	for (var i = 0; i < arrElements.length; i++) {
		for (var j = 0; j < arrAttributes.length; j++) {
			var strValue = (arrElements[i].getAttribute(arrAttributes[j]) || '')
			if ((strValue != null) && (strValue != '')) {
				arrResult.push(arrElements[i]);
				break;
			}
		}
	}
	return arrResult;
}



/********************************
 * FUNCTION:	get_inner_html
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.02.24
 * DESCRIPTION:	This function gets the innerHTML of the given element, ignoring content added by GoRoam.
 * PARAMETERS:	elElement   The HTML element to examine.
 * RETURNS:	    string      The innerHTML of the element, without additional GoRoam elements.
 */
goroam.dhtml.get_inner_html = function(elElement) {
  var strResult = '';
  if (elElement) {
    var elNode = elElement.cloneNode(true);
    var arrRemove = goroam.dhtml.get_elements_in_class('map_prompt', elNode);
    for (var i = 0; i < arrRemove.length; i++) arrRemove[i].parentNode.removeChild(arrRemove[i]);
    strResult = elNode.innerHTML;
  }
  return strResult;
}



/********************************
 * FUNCTION:	get_inner_text
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.02.24
 * DESCRIPTION:	This function gets the inner text of the given element, based on browser.
 * PARAMETERS:	elElement   The HTML element to examine.
 * RETURNS:	    string      The inner text of the element, without HTML markup.
 */
goroam.dhtml.get_inner_text = function(elElement) {
  var strResult = '';
  if (elElement) {
    if (elElement.innerText) {
      strResult = elElement.innerText;
    } else if (elElement.textContent) {
      strResult = elElement.textContent;
    }
  }
  return strResult;
}



/********************************
 * FUNCTION:	get_event
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2006.02.20
 * DESCRIPTION:	This function gets the event, based on how the browser passes the event.
 * PARAMETERS:	Event Object	The calling event.
 * RETURNS:	Event Object	The calling event.
 */
goroam.dhtml.get_event = function(evEvent) {
	var evResult = evEvent ? evEvent : window.event;
	return evResult;
}



/********************************
 * FUNCTION:	get_select_value
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.03.02
 * DESCRIPTION:	This function gets the value of the given select box.
 * PARAMETERS:	elSelect	The select box in question.
 * RETURNS:	    string	  The selected value.
 */
goroam.dhtml.get_select_value = function(elSelect) {
  return elSelect.options[elSelect.selectedIndex].value;
}



/********************************
 * FUNCTION:	is_in_class
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2008.10.16
 * DESCRIPTION:	This function tells whether or not the given element is in the given class.
 * PARAMETERS:	elElement			The HTML element to examine.
 *              strClassName	The class to look for.
 *				      bolNoPrefix   Optional. True to ignore the GoRoam class prefix; False to use it.  Defaults to false.
 * RETURNS:		  boolean			  True is the class matches; false if not.
 */
goroam.dhtml.is_in_class = function(elElement, strClassName, bolNoPrefix) {
	var bolResult = false;
  strClassName = (strClassName || '');
  var strClassAttr = (goroam.browser.host_info().name == goroam.enm.browsers.ie ? 'className' : 'class');
	var strElementClass = (elElement.getAttribute(strClassAttr) || '');
  if ((strClassName != '') && (strElementClass != '')) {
    if (!bolNoPrefix) strClassName = goroam.string.add_prefix_to_all_words(strClassName, 'gr_');
    var arrClassName = strClassName.compress_whitespace().split(' ');
    for (var i = 0; i < arrClassName.length; i++) {
	    if (strElementClass.search(RegExp('\\b' + arrClassName[i] + '\\b', 'i')) >= 0) {
        bolResult = true;
        break;
      }
    }
	}
	return bolResult;
}



/********************************
 * FUNCTION:	set_mouseover, unset_mouseover
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.02.18
 * DESCRIPTION:	This function adds a mouseover class to the given HTML element.  The actual
 *              effects should be set in the CSS.  The 'mouseover' class can optionally be overridden by
 *              setting the '__mouseover_class_name' property of the calling HTML element.
 * PARAMETERS:	elElement   Optional. The HTML element to highlight.  Defaults to the calling object.
 * RETURNS:	none
 */
goroam.dhtml.set_mouseover = function() {
  var strClassName = (this.__mouseover_class_name || 'mouseover');
  var strOutClassName = (this.__mouseout_class_name || '');
  goroam.dhtml.edit_class_name(this, strClassName, strOutClassName);
  this.__mouseover = true;
}
goroam.dhtml.unset_mouseover = function() {
  var strClassName = (this.__mouseover_class_name || 'mouseover');
  var strOutClassName = (this.__mouseout_class_name || '');
  goroam.dhtml.edit_class_name(this, strOutClassName, strClassName);
  this.__mouseover = false;
}



/********************************
 * FUNCTION:	outer_html
 * WRITTEN BY:	Ryan Martin
 * WRITTEN ON:	2009.02.16
 * DESCRIPTION:	This function gets the html code of the entire element.
 * PARAMETERS:	elElement   The HTML element for which to grab the HTML.
 * RETURNS:	none
 */
goroam.dhtml.outer_html = function(elElement) {
  var strResult = '';
  if (elElement) {
    if (elElement.outerHTML) {
      strResult = elElement.outerHTML;
    } else {
      var elTempDiv = document.getElementById('gr_outer_html');
      if (!elTempDiv) {
        elTempDiv = document.createElement('gr_outer_html');
        elTempDiv.style.display = 'none';
        elTempDiv.appendChild(elElement.cloneNode(true));
        strResult = elTempDiv.innerHTML;
        elTempDiv.innerHTML = '';
      }
    }
  }
  return strResult;
}

google.setOnLoadCallback(function() {goroam.google.maps.load(goroam.google.maps.load_from_page);});


